STM32CubeWBA: Memory management

Revision as of 15:30, 2 March 2023 by Registered User (→‎Interface)
Under construction.png Coming soon



1. Introduction

An enhanced version of dynamic allocation is available with the STM32WBA: the Advanced Memory Manager. It eases dynamic allocations in a multitask/shared environment without adding much complexity.

The new features provided by AMM are the following:

  • Support several Virtual Memories
  • Minimum size warranty per Virtual Memory (QOS)
  • Callback in case of memory allocation failure

2. Features

The Advanced Memory Manager comes on top of a Basic Memory Manager - Understand here anything capable of providing basic memory allocation features such as Alloc and Free – and add some new capabilities on top of it.

2.1. Basic Memory Manager and Heap

The Basic Memory Manager (BMM)
Can be anything that is capable to allocate/free memory
Shall at least support the capability to merge into one single memory two continuous memories that has been freed (coalescence)
Shall be provided by the user before any use of the AMM via the registration function
The Heap
Can either be a dedicated memory provided by the application, or the legacy default Heap provided by most application at link time
Its size shall be at least equal to the sum of all Virtual Memories size

2.2. Virtual Memories and Shared Pool

In a concurrent execution application, dynamic allocation can encounter some issues with heap sharing. For instance, a task may allocate a major part of the available heap leading to allocation failure for all other tasks requesting memory. To avoid that kind of issue, the Advanced Memory Manager introduces the concept of virtual memories and shared pool.

The Virtual memory
Dedicated user memory pool, ie: specific VM IDs.
There to ensure to users the memory amount needed for minimal execution - Heap resource only available for this Virtual Memory ID.
The Shared pool
Mutual memory pool, ie: Can be used by any Virtual Memory ID.
Provide memory for an optimal execution.
Its size corresponds to the BMM pool size minus the space required for the Virtual memories.

For every operations, the user fills his Virtual Memory ID. This helps determine its identity and allow the AMM to identify how much space is remaining in this Virtual Memory.
However, once his Virtual Memory quota is consumed - Sum of all the user allocations = Virtual Memory size -, the user can still pursue with allocation requests. Those requests will take place in the Shared Pool area and are, here, fully dependent of the others users sympathy - Meaning that the request may or may not succeed depending on the availability of resources.

2.3. Retry callback

During program execution, users may encounter some memory allocation failure - Not enough memory available. Instead of setting up a polling mechanism, in an allocation request, the AMM offers the possibility to register a callback.
This callback  inform the requester(in an asynchronous way) that some space has been freed - Either in the shared pool or in its dedicated virtual pool - and a new allocation request can be submitted.
In some cases, the memory can be freed from a different context than the one it has been allocated from. The Callback should not implement any active code and should be used only to set up a new allocation request from the main context.

2.3.1. Single callback case

2.3.2. Multiple callback case

3. Interfaces

Here comes a list of the available functions for the different Flash management modules:

3.1. Flash Driver

3.1.1. Flash driver error codes

FD_FlashOp_Status_t
Error code Description
FD_FLASHOP_SUCCESS Flash operation success
FD_FLASHOP_FAILURE Flash operation failure

3.1.2. Flash driver functions

FD_SetStatus

Description

Update Flash Control status.
Syntax
void FD_SetStatus(FD_Flash_ctrl_bm_t Flags_bm, FD_FLASH_Status_t Status);
Parameters
[in] Flags_bm
Type: FD_Flash_ctrl_bm_t
Description: Bit mask identifying the caller
[in] Status
Type: FD_FLASH_Status_t
Description: Action requested (enable or disable flash access)
Return Value
None
FD_WriteData

Description

Write a block of 128 bits (4 32-bit words) in Flash
Syntax
FD_FlashOp_Status_t FD_WriteData(uint32_t Dest, uint32_t Payload);
Parameters
[in] Dest
Type: uint32_t
Description: Address where to write in Flash (128-bit aligned)
[in] Payload
Type: uint32_t
Description: Address of data to be written in Flash (32-bit aligned)
Return Value
FD_FlashOp_Status_t
FD_EraseSectors

Description

Erase one sector of Flash
Syntax
FD_FlashOp_Status_t FD_EraseSectors(uint32_t Sect);
Parameters
[in] Sect
Type: uint32_t
Description: Identifier of the sector to erase
Return Value
FD_FlashOp_Status_t

3.2. RF Timing Synchro

3.2.1. RF Timing synchro error codes

RFTS_Cmd_Status_t
Error code Description
RFTS_CMD_OK The RF Timing synchronization command was successfully executed
RFTS_WINDOW_REQ_FAILED The RF Timing synchronization module failed to register the window request
RFTS_WINDOW_REL_ERROR An error occurred during the window release procedure

3.2.2. RF Timing synchro functions

RFTS_ReqWindow

Description

Request a time window to the SNPS FW LL
Syntax
RFTS_Cmd_Status_t RFTS_ReqWindow(uint32_t Duration, void (*Callback)(void));
Parameters
[in] Duration
Type: uint32_t
Description: Duration in us of the time window requested
[in] Callback
Type: void (*Callback)(void)
Description: Callback to be called when time window is allocated
Return Value
RFTS_Cmd_Status_t
RFTS_RelWindow

Description

Execute necessary tasks to allow the time window to be released
Syntax
RFTS_Cmd_Status_t RFTS_RelWindow(void);
Parameters
None
Return Value
RFTS_Cmd_Status_t

3.3. Flash Manager

3.3.1. Flash manager error codes

FM_Cmd_Status_t
Error code Description
FM_OK The Flash Manager is available and a window request is scheduled
FM_BUSY The Flash Manager is busy and the caller will be called back when it is available
FM_ERROR An error occurred while processing the command

3.3.2. Flash manager functions

FM_Write

Description

Request the Flash Manager module to initiate a Flash Write operation
Syntax
FM_Cmd_Status_t FM_Write(uint32_t *Src, uint32_t *Dest, int32_t Size, FM_CallbackNode_t *CallbackNode);
Parameters
[in] Src
Type: uint32_t *
Description: Address of the data to be stored in FLASH. It shall be 32bits aligned
[in] Dest
Type: uint32_t *
Description: Address where the data shall be written. It shall be 128bits aligned
[in] Size
Type: int32_t
Description: This is the size of data to be written in Flash. The size is a multiple of 32bits (size = 1 means 32bits)
[in] CallbackNode
Type: FM_CallbackNode_t *
Description: Pointer to the callback node for storage in list
Return Value
FM_Cmd_Status_t
FM_Erase

Description

Request the Flash Manager module to initiate a Flash Erase operation
Syntax
FM_Cmd_Status_t FM_Erase(uint32_t FirstSect, uint32_t NbrSect, FM_CallbackNode_t *CallbackNode);
Parameters
[in] FirstSect
Type: uint32_t
Description: Index of the first sector to erase
[in] NbrSect
Type: uint32_t
Description: Number of sector to erase
[in] CallbackNode
Type: FM_CallbackNode_t *
Description: Pointer to the callback node for storage in list
Return Value
FM_Cmd_Status_t
FM_BackgroundProcess

Description

Execute Flash Manager background tasks
Syntax
void FM_BackgroundProcess (void);
Parameters
None
Return Value
None
FM_ProcessRequest

Description

Request to the user scheduler to be scheduled
Syntax
void FM_ProcessRequest (void);
Parameters
None
Return Value
None

3.4. Simple NVM Arbiter

3.4.1. Simple NVM Arbiter error codes

SNVMA_Cmd_Status_t
Error code Description
SNVMA_ERROR_OK No error code
SNVMA_ERROR_NOK Error that occurred before any check
SNVMA_ERROR_NOT_INIT Error code for a module not yet initialized
SNVMA_ERROR_ALREADY_INIT Error code for a module already initialized
SNVMA_ERROR_CMD_PENDING Error code for a command pending
SNVMA_ERROR_NVM_NULL Error code for a NULL NVM pointer
SNVMA_ERROR_NVM_NOT_ALIGNED Error code for a not aligned NVM address
SNVMA_ERROR_NVM_OVERLAP_FLASH Error code for a NVM size that overlaps flash capacities
SNVMA_ERROR_NVM_BUFFER_FULL Error code for a full NVM Buffer
SNVMA_ERROR_NVM_BANK_EMPTY Error code for an empty NVM Buffer
SNVMA_ERROR_NVM_BANK_CORRUPTED Error code for a corrupted NVM Buffer
SNVMA_ERROR_CRC_INIT Error code for a CRC initialization fail
SNVMA_ERROR_BUFFERID_NOT_KNOWN Error code for an unknown Buffer ID
SNVMA_ERROR_BUFFERID_NOT_REGISTERED Error code a non-registered Buffer ID
SNVMA_ERROR_BUFFER_NULL Error code for a NULL Buffer pointer
SNVMA_ERROR_BUFFER_NOT_ALIGNED Error code for a not aligned Buffer address
SNVMA_ERROR_BUFFER_SIZE Error code for a Buffer size that is not OK
SNVMA_ERROR_BUFFER_CONFIG_MISSMATCH Error code for a mismatch between the registered buffer and the buffer to restore
SNVMA_ERROR_FLASH_ERROR Error code for a flash error
SNVMA_ERROR_UNKNOWN Error code for an unknown error

3.4.2. Simple NVM Arbiter functions

SNVMA_Init

Description

Initialize the Simple NVM Arbiter
Syntax
SNVMA_Cmd_Status_t SNVMA_Init (const uint32_t * p_NvmStartAddress);
Parameters
[in] p_NvmStartAddress
Type: const uint32_t *
Description: Start address of the NVM to work with - Shall be aligned 128 bits
Return Value
SNVMA_Cmd_Status_t
SNVMA_Register

Description

Register a user buffer to a NVM.
Buffer IDs are hardcoded, please refer to SNVMA_BufferId_t enumeration
Syntax
SNVMA_Cmd_Status_t SNVMA_Register (const SNVMA_BufferId_t BufferId,
                                   const uint32_t * p_BufferAddress,
                                   const uint32_t BufferSize);
Parameters
[in] BufferId
Type: const SNVMA_BufferId_t
Description: Id of the user which ask for buffer registration
[in] p_BufferAddress
Type: const uint32_t *
Description: Address of the buffer to be registered - Shall be aligned 32 bits
[in] BufferSize
Type: const uint32_t
Description: Size of the buffer to be registered - Shall be a multiple of 32bits
Return Value
SNVMA_Cmd_Status_t
SNVMA_Restore

Description

Restore a user buffer from a NVM.
The user buffer information shall first be provided by calling SNVMA_Register.
Buffer IDs are hardcoded, please refer to SNVMA_BufferId_t enumeration
Syntax
SNVMA_Cmd_Status_t SNVMA_Restore (const SNVMA_BufferId_t BufferId);
Parameters
[in] BufferId
Type: const SNVMA_BufferId_t
Description: Id of the user which ask for buffer registration
Return Value
SNVMA_Cmd_Status_t
SNVMA_Write

Description

Register a user buffer to a NVM.
The user buffer information shall first be provided by calling SNVMA_Register.
Buffer IDs are hardcoded, please refer to SNVMA_BufferId_t enumeration.
A buffer write request cannot be scheduled once its NVM is already on a write operation. This will lead to a SNVMA_OPERATION_FAILED callback status.
Syntax
SNVMA_Cmd_Status_t SNVMA_Write (const SNVMA_BufferId_t BufferId,
                                void (* Callback) (SNVMA_Callback_Status_t));
Parameters
[in] BufferId
Type: const SNVMA_BufferId_t
Description: Id of the user which ask for buffer registration
[in] Callback
Type: void (* Callback) (SNVMA_Callback_Status_t))
Description: Callback function for operation status return - Can be NULL
Return Value
SNVMA_Cmd_Status_t

4. How to

The following chapters explain "how to" configure and use the Advanced Memory Manager.

4.1. Initialize the AMM

Before any use, AMM needs to be setup and there is few steps to do so.

4.1.1. Basic Memory Manager selection

First things first, you have to determine the Basic Memory Manager you are going to use.
As said above, it can be anything capable of allocation and free. The only mandatory point is coalescence.
In the following examples, the selected BMM will be the Memory manager utility - Already available in the current release under the memory manager folder- UTIL_MM_XXX ().

4.1.2. Basic Memory Manager registration

Once the BMM is identified, you have to register its functions to the AMM. To do so we need to create function wrappers since our BMM will not always fully match the expected function of the AMM register function:

Definition:
static void AMM_WrapperInit (uint32_t * const p_PoolAddr, const uint32_t PoolSize);

static uint32_t * AMM_WrapperAllocate (const uint32_t BufferSize);

static void AMM_WrapperFree (uint32_t * const p_BufferAddr);
Implementation:
static void AMM_WrapperInit (uint32_t * const p_PoolAddr, const uint32_t PoolSize)
{
  UTIL_MM_Init ((uint8_t *)p_PoolAddr, ((size_t)PoolSize * sizeof(uint32_t)));
}

static uint32_t * AMM_WrapperAllocate (const uint32_t BufferSize)
{
  return (uint32_t *)UTIL_MM_GetBuffer (((size_t)BufferSize * sizeof(uint32_t)));
}

static void AMM_WrapperFree (uint32_t * const p_BufferAddr)
{
  UTIL_MM_ReleaseBuffer ((void *)p_BufferAddr);
}
Info white.png Information
Beware that operation with AMM are achieved on a 32bits size reference


Once wrappers are defined, register function is the next function to implement - Definition is already done in the AMM header file. The AMM will call this function at its initialization so we have to provide pointer onto the newly declared wrappers:

Implementation:
void AMM_RegisterBasicMemoryManager (AMM_BasicMemoryManagerFunctions_t * const p_BasicMemoryManagerFunctions)
{
  /* Fulfill the function handle */
  p_BasicMemoryManagerFunctions->Init = AMM_WrapperInit;
  p_BasicMemoryManagerFunctions->Allocate = AMM_WrapperAllocate;
  p_BasicMemoryManagerFunctions->Free = AMM_WrapperFree;
}

With this steps over, the BMM will be linked up with the AMM and fully operational. Next step is the configuration of the AMM.

Info white.png Information
Please give a look to function section for more information on the register function.

4.1.3. Configure and Initialize the AMM

The Advanced memory manager configuration is done at initialization and the parameters are the following:

typedef struct AMM_InitParameters
{
  /* Address of the pool of memory to manage */
  uint32_t * p_PoolAddr;
  /* Size of the pool with a multiple of 32bits.

     ie: PoolSize = 4; TotalSize = PoolSize * 32bits
                                 = 4 * 32bits
                                 = 128 bits */
  uint32_t PoolSize;
  /* Number of Virtual Memory to create */
  uint32_t VirtualMemoryNumber;
  /* List of the Virtual Memory configurations */
  AMM_VirtualMemoryConfig_t a_VirtualMemoryConfigList[];
}AMM_InitParameters_t;

The AMM configuration is a two steps procedure:

  1. Establish the Virtual Memory + Shared pool configurations (How many VMs, What size, etc)
  2. Adapt the pool size and location of the AMM pool according to the configuration firstly determined
4.1.3.1. Virtual Memory and Shared pool configurations
These two are the backbone of the AMM configuration. You will have to determine both and their characteristics to pursue with the AMM initialization.
Virtual Memories configurations
There are 3 characteristics to identify: The number of VMs, the proper size of each VM and their IDs:
The Number of VMs
To determine the number of needed Virtual Memories, you have to identify the number of process that need their own heap for their nominal operating. You shall consider one Virtual Memory for one of these process.
The Size of each VMs
The size of each Virtual Memory will be determined by the process needs. Each Virtual Memory shall be sized to the process nominal heap value. Do not forget that the Virtual Memory size is set on a 32bits basis.
The ID of each VMs
For each VM, you can define an unique ID. This will be used to access the proper Virtual Memory during memory operation.
Those characteristics can afterward be fulfilled in the AMM initialization parameters structure:
For the Number of Virtual Memories:
  /* Number of Virtual Memory to create */
  uint32_t VirtualMemoryNumber;
For the proper configuration of each Virtual Memory, a structure like this shall be instanced:
typedef struct AMM_VirtualMemoryConfig
{
  /* ID of the Virtual Memory */
  uint8_t Id;
  /* Size of the Virtual Memory buffer with a multiple of 32bits.

     ie: BufferSize = 4; TotalSize = BufferSize * 32bits
                                   = 4 * 32bits
                                   = 128 bits */
  uint32_t BufferSize;
}AMM_VirtualMemoryConfig_t;
and provided to the AMM Initialization parameter:
  /* List of the Virtual Memory configurations */
  AMM_VirtualMemoryConfig_t a_VirtualMemoryConfigList[];
Shared Pool configuration
Shared Pool Size
Regarding the Shared Pool size, its size is up to you. The shared pool is designed to allow an optimal execution of each process that need a little bit of heap, once in a while, to perform better.
The Shared Pool configuration is not a proper parameter of AMM initialization. However, it must be defined to determine the whole AMM required pool space. This concept also ease the AMM comprehension and enhance its configuration - This leads to a better optimization of the required space.
4.1.3.2. AMM pool configuration
As seen above, the AMM pool size is mostly dependent of the VM and Shared Pool configurations.
Nonetheless, there is also a management part that need to be considered in the pool size computation.
AMM management part
For operating purposes, the AMM allocates some of the heap provided to it. For each VM, it creates an info element.
Thus user shall consider at the initialization more space for the pool.
Considering all those elements, the AMM pool size representation is as follow:
Pool Size computation
With all the points enounced above, you can compute the adequate Pool Size following this formula:
#define CFG_AMM_POOL_SIZE = (CFG_AMM_NUMBER_OF_VM * AMM_VIRTUAL_INFO_ELEMENT_SIZE) + \
                             CFG_AMM_SHARED_POOL_SIZE + \
                             CFG_AMM_SUM_OF_VMS_SIZE
and afterward fulfill the AMM init parameter, remembering that the size is on a 32bits basis:
  /* Size of the pool with a multiple of 32bits.

     ie: PoolSize = 4; TotalSize = PoolSize * 32bits
                                 = 4 * 32bits
                                 = 128 bits */
  uint32_t PoolSize;
Start Address
The start address can either be a heap located address or a static buffer used as a memory pool.
For instance, use a static allocated buffer:
static uint32_t AMM_Pool[CFG_AMM_POOL_SIZE];
Info white.png Information
Please give a look to function section for more information on the initialization function.

4.2. Allocate memory

The allocation function differs a bit from the standard malloc. It adds:

  • A function error code that informs the user on the state of the operation.
  • The possibility to register a callback function in case of allocation failure.

The function can be called with or without a Virtual Memory ID and with or without the registration of a callback. The following examples will highlight these facts.

Allocation with a Virtual Memory ID and with a callback registration
This is the typical way of calling the allocation function. In this way, the allocation takes places in the Virtual Memory - Or Shared Pool if the first one is full - and the user has his callback registered in case of allocation failure - Due to not enough space available. This one will be invoked once a space liberation occurs.
Here is the example:
uint32_t * p_AllocBuffer = NULL;

uint32_t funcReturn = AMM_ERROR_NOK;

VirtualMemoryId = 0x1;
BufferSize = 0xA;

funcReturn = AMM_Alloc (VirtualMemoryId,
                        BufferSize,
                        &p_AllocBuffer,
                        &CallBackElt);

if (funcReturn == AMM_ERROR_OK)
{
  // Do stuff with the brand new buffer
  ...
}
/*
else if (funcReturn == AMM_XXX)
{
  // Manage the AMM_XXX error
  ...
}
else
{
  ...
}
*/
with the callback parameter looking like this:
static AMM_VirtualMemoryCallbackFunction_t CallBackElt = 
{
  .Header =
  {
    .next = NULL,
    .prev = NULL
  },
  .Callback = Callback
};
and the callback function could be implemented as this:
void Callback (void)
{  
  /* Set event to notify that a new allocation can be requested */
  UTIL_SEQ_SetEvt (1u << AMM_CALLBACK_EVT_BM);
}
Allocation without a Virtual Memory ID and without a callback registration
This is most straight forward way, with no ID and no Callback registration. The allocation will take place in the Shared Pool and in case of failure, the user would need to execute a new request by himself.
Here is the example:
uint32_t * p_AllocBuffer = NULL;

uint32_t funcReturn = AMM_ERROR_NOK;

BufferSize = 0xA;

funcReturn = AMM_Alloc (AMM_NO_VIRTUAL_ID,
                        BufferSize,
                        &p_AllocBuffer,
                        NULL);

if (funcReturn == AMM_ERROR_OK)
{
  // Do stuff with the brand new buffer
  ...
}
/*
else if (funcReturn == AMM_XXX)
{
  // Manage the AMM_XXX error
  ...
}
else
{
  ...
}
*/

4.3. Free memory

The free operation is rather simple, the only difference with the one from the standard lib is that the AMM_Free has a returned error code that can be analyzed.
Here the example:

uint32_t funcReturn = AMM_ERROR_NOK;

funcReturn = AMM_Free (p_AllocatedBuffer);

if (funcReturn == AMM_ERROR_OK)
{
  // Do stuff with the brand new buffer
  ...
}
/*
else if (funcReturn == AMM_XXX)
{
  // Manage the AMM_XXX error
  ...
}
else
{
  ...
}
*/

5. Revisions

Rev. number Description
0.1 First wiki version.