Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

Protecting memory regions in PSoC6

Protecting memory regions in PSoC6

DheerajK_81
Moderator
Moderator
Moderator
First comment on KBA First comment on blog 5 questions asked

Protecting memory regions in PSoC6

PSoC6 devices have multiple types of protection units to control erroneous or unauthorized access to memory and peripheral registers.

  • CM4 and CM0+ have ARM MPUs for protection at the bus master level.
  • Other bus masters (Crypto and Test Controller) use additional MPUs designed by Cypress similar to ARM MPUs.
  • Shared memory protection units (SMPUs) help implement memory protection for memory/ resources that are shared among multiple bus masters.
  • Peripheral protection units (PPU) that are designed for protecting the peripheral register space.

 

We will be learning mostly about SMPU in this blog, but most of the things apply to the PPU as well. Irrespective of the type of protection unit you intend to use, they are defined by the same fundamental structure:

  • Address region
  • Access control attributes

 

The address region is defined by:

  • The base address of a region as specified by ADDR.ADDR
  • The size of a region as specified by ATT.REGION_SIZE
  • Individual disables for eight sub-regions within the region, as specified by ADDR.SUBREGION_DISABLE

 

Whenever you define any address region, you need to take care that the base address is aligned with the region size. For example, if you define the base address as 0x1002_0000 it is 128KB aligned, but not 256KB or higher byte aligned. This means that you can use 0x1002_0000 as the base address only if the region size you define is 128KB or lesser to be protected.

 

To calculate if a memory address is aligned with the region size, find out where the first bit is 1 from the LSB. For example, for base address 0x1002_0000 the binary value is:

0001 0000 0000 0010 0000 0000 0000 0000

 

The position of 1 here is at index 17 counting from 0 and the value of 2^17 is 128KB. So, it is 128KB aligned at max. It is also 64KB, 32KB, …. 2B aligned but max 128KB.

 

Specifying a value that is invalid causes the base address to automatically change according to the region size specified. For any device, the flash usually starts from 0x1000_0000. Check the Architecture TRM of the particular device for more information on the memory organization.

 

If the base address is defined as 0x1002_0000 (128KB aligned) and the region size is set as 512KB, then since the base address is not aligned, all the bits [0:18] will be ignored and only bits [19:31] will be taken into consideration. This means that the invalid region size causes the base address to be set as 0x1000_0000. Note that 0x1000_0000 is aligned with all region sizes max being 2^28 = 256MB aligned.

 

The region to protect is always a power of 2 in the range of [256B to 4GB]. Check Architecture TRM of your device for these range values.

 

Let’s say you want to protect the memory region between 0x1002_0000 to 0x1006_0000. How do you do it?

mem1.png

 

First, find out the memory alignment of the starting address.

 

0x1002_0000 is 128KB aligned. The region size between 0x1002_0000 to 0x1006_0000 is 0x40000 (256KB). Even though the region size to be protected is 256KB, you can only set the region size for the SMPU to be 128KB max to adhere to the alignment with the base address. So, you cannot use this address as the base address.

 

You need to select another base address such that:

  • It has a higher byte alignment (>128KB)
  • It is lower than the lower address you want to protect (i.e 0x1002_0000)
  • The region size covers the highest address (i.e 0x1006_0000)

 

This leaves us with one option. Using 0x1000_0000 as the base address and setting the region size as 512KB.

 

So, configure an SMPU with the base address as 0x1000_0000 and region size as 512KB. Now how do you protect just the region between 0x1002_0000 and 0x1006_0000?

 

The region size you define for an SMPU is divided into 8 equal sub-regions. In this case, 512/8 = 64KB sub-region. So, the 8 sub-regions will have the address mapping as shown:

mem2.png

 

The sub-regions 2 to 5 cover the regions that we intend to protect. So the sub-regions value 0xC3 (1100 0011) is chosen below where the value 1 in the bit means the region to be disabled or unprotected.

 

The SMPU structure cy_stc_smpu_cfg_t is used to configure the SMPU parameters. It will be defined as follows:

 

static const cy_stc_smpu_cfg_t flash_protect = {

          .address = (uint32_t *) 0x10000000,            

          .regionSize = CY_PROT_SIZE_512KB,                

          .subregions = 0xC3,                               

          .userPermission = CY_PROT_PERM_RX,             

          .privPermission = CY_PROT_PERM_RX,

          .secure = false,

          .pcMatch = false,

          .pcMask =  CY_PROT_PCMASK1

};

 

The .userpermission and .privpermission protection attributes implement a protection scheme to distinguish between read/write or execute access to a specified memory region by a user code or privileged code. The privilege code is typically the kernel code in an RTOS environment.

 

The .secure protection attribute distinguishes if accesses by a secure processor are enabled or not.

 

The same logic applies for PPU and MPU, it all depends on where the memory region belongs, and which protection unit will be the most suitable. You can use SMPU to protect peripheral memory spaces, it is not a problem, but PPU is available only for the protection of peripherals and could have been used instead.

 

Once the structure is defined you need to call the following PDL APIs:

Cy_Prot_ConfigSmpuSlaveStruct(PROT_SMPU_SMPU_STRUCT2, &flash_protect);

Cy_Prot_EnableSmpuSlaveStruct(PROT_SMPU_SMPU_STRUCT2);

 

The index of the structure highlighted above is used to define the priority of the SMPU. Here the priority is 2.

 

There are 15 SMPUs that you can use in PSoC6 to protect a memory region. In cases where you have two SMPUs protecting the same region, the priority is for the SMPU structure with the highest number. The highest SMPU priority is 15 and the lowest is 0.

 

Next, we need to configure the Protection Units and the Bus Masters. A protection unit is a hardware block that implements the protection scheme. Each protection unit has one or more protection structures associated with them. A protection structure is a group of registers which define a memory region (address and size) and a set of protection attributes that are to be applied to it.

 

Protection Context (PC) provides a more precise way of applying memory restrictions. A bus master with a Protection Context 0 can access any memory or register region no matter how the protection units are configured. PC=0 is a special case that allows any bus master to have full access to the entire memory space including registers.

 

The PC state works together with protection units (SMPU, PPU). Each of the protection units has a field called .pcMask which is used to specify which protection context can have access to the memory region.

 

There are six bus masters in PSoC6:

  • CM0+ Core
  • CM4 Core
  • Crypto Block
  • DataWire0 (DMA)
  • DataWire1 (DMA)
  • Test Controller (TC)

 

Their macros are available through the enum en_prot_master_t.

 

typedef enum

{

        CPUSS_MS_ID_CM0                 =  0,

        CPUSS_MS_ID_CRYPTO          =  1,

        CPUSS_MS_ID_DW0                 =  2,

        CPUSS_MS_ID_DW1                 =  3,

        CPUSS_MS_ID_CM4                 = 14,

        CPUSS_MS_ID_TC                    = 15

} en_prot_master_t;

 

 

You can assign a particular PC using the API shown below where the CM0+ is assigned the PC 1.

Cy_Prot_ConfigBusMaster(CPUSS_MS_ID_CM0, false, false, CY_PROT_PCMASK1);

 

To activate the PC, you will need to call:

Cy_Prot_SetActivePC(CPUSS_MS_ID_CM0, CY_PROT_PC1);

 

Let’s say you have defined an SMPU memory region as follows:

 

static const cy_stc_smpu_cfg_t main_flash_prot_cfg_s = {

        .address = (uint32_t *) 0x10000000,

        .regionSize = CY_PROT_SIZE_512KB,                

        .subregions = 1,                               

        .userPermission = CY_PROT_PERM_RX,             

        .privPermission = CY_PROT_PERM_RX,

        .secure = false,

        .pcMatch = false,

        .pcMask =  CY_PROT_PCMASK1 | CY_PROT_PCMASK2  

};

 

All the values in the above snippets like CY_PROT_PERM_RX, CY_PROT_SIZE_512KB, etc. are macros that are already defined internally. You don't need to define them.

 

This memory region protected by the SMPU as defined above provides access to only bus masters that have the PC value of 1, 2, and 0. To understand this better, find the project “PSoC6_Memory_Protection.zip” attached with this blog. The project demonstrates how you can protect a memory region in the CM0p memory space from being accessed by CM4. The project details can be found in the ReadMe PDF file in the project folder.

 

See if you can relate your understanding with the project implementation!

 

Now here’s something interesting to know and try. Although it seems like we have protected the memory regions of CM0p from being accessed by CM4 in the project, we have not protected the PC Mask registers themselves. Any of the bus masters can change this and set their current PC mask value to whatever they want and have access to the required memory regions.

 

So, usually, you would set up all the memory protections and also protect the PC Mask registers while CM0p is still PC=0 and then switch the bus master to something other than PC=0. That way you have locked the door and thrown away the key

 

Stay tuned for the next blog that reveals how you can do this! For clues, use the Protection Units section in the PSoC6 Architecture TRM.

Attachments
988 Views
Comments
Sharath
Moderator
Moderator 25 solutions authored 100 sign-ins 50 replies posted
Moderator

@DheerajK_81 : Great read, concise and precise.