This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Models and Subscription Lists are written to flash on every start-up

When starting up the device with the nRF5 SDK 3.2.0 for mesh, even though the model and subscription list state is loaded from flash, it is stored again right afterwards.

I think the problem is in the access_model_add and the mesh_stack_init functions:
The access_model_add function allocates a new model in case it is not recovered from flash and sets it as outdated, so its state is eventually written to flash. The function fails, if m_access_model_config_frozen is true.

uint32_t access_model_add(const access_model_add_params_t *p_model_params,
                          access_model_handle_t *p_model_handle) {
/* ... */
if (0 == p_model_params->opcode_count && NULL != p_model_params->p_opcode_handlers) {
    return NRF_ERROR_INVALID_LENGTH;
  } else if (p_model_params->element_index >= ACCESS_ELEMENT_COUNT) {
    return NRF_ERROR_NOT_FOUND;
  } else if (m_access_model_config_frozen || (element_has_model_id(p_model_params->element_index, p_model_params->model_id, p_model_handle) && ACCESS_INTERNAL_STATE_IS_ALLOCATED(m_model_pool[*p_model_handle].internal_state))) {
    return NRF_ERROR_FORBIDDEN;
  } else if (!opcodes_are_valid(p_model_params->p_opcode_handlers, p_model_params->opcode_count)) {
    return NRF_ERROR_INVALID_PARAM;
  } else if (*p_model_handle == ACCESS_HANDLE_INVALID) /* The model was not recovered from the flash */
  {
    *p_model_handle = find_available_model();
    if (ACCESS_HANDLE_INVALID == *p_model_handle) {
      return NRF_ERROR_NO_MEM;
    }

    m_model_pool[*p_model_handle].model_info.publish_address_handle = DSM_HANDLE_INVALID;
    m_model_pool[*p_model_handle].model_info.publish_appkey_handle = DSM_HANDLE_INVALID;
    m_model_pool[*p_model_handle].model_info.element_index = p_model_params->element_index;
    m_model_pool[*p_model_handle].model_info.model_id.model_id = p_model_params->model_id.model_id;
    m_model_pool[*p_model_handle].model_info.model_id.company_id = p_model_params->model_id.company_id;
    m_model_pool[*p_model_handle].model_info.publish_ttl = ACCESS_TTL_USE_DEFAULT;
    increment_model_count(p_model_params->element_index, p_model_params->model_id.company_id);
    ACCESS_INTERNAL_STATE_OUTDATED_SET(m_model_pool[*p_model_handle].internal_state);
  }
}

However, in the mesh_stack_init function, the models are first initialized (which call access_model_add internally) and then the state is recovered from flash:

uint32_t mesh_stack_init(const mesh_stack_init_params_t * p_init_params,
                         bool * p_device_provisioned)
{
    /* ... */
    
    /* Initialize the access layer */
    dsm_init();
    access_init();

    /* Initialize the configuration server */
    status = config_server_init(p_init_params->models.config_server_cb);
    if (status != NRF_SUCCESS)
    {
        return status;
    }

    /* Initialize the health server for the primary element */
    status = health_server_init(&m_health_server, 0, DEVICE_COMPANY_ID,
                                p_init_params->models.health_server_attention_cb,
                                p_init_params->models.p_health_server_selftest_array,
                                p_init_params->models.health_server_num_selftests);
    if (status != NRF_SUCCESS)
    {
        return status;
    }

    /* Give application opportunity to initialize application specific models */
    if (p_init_params->models.models_init_cb != NULL)
    {
        p_init_params->models.models_init_cb();
    }

    /* Load configuration, and check if the device has already been provisioned */
    mesh_config_load();

    (void) dsm_flash_config_load();

    if (access_flash_config_load())
    {
        access_flash_config_store();
    }

    /* ... */
    return NRF_SUCCESS;
}


The access_flash_config_load function will set m_access_model_config_frozen to true, which means, it has to be called after the models were initialized.

In consequence, access_model_add will always allocate a new model and assume its state has not been recovered from flash. It will then set the outdated flag which stores the model to flash again when access_flash_config_store is called. The internal restore_acquired_model function only sets the restored flag, but does not remove the outdated flag, which was set before.

I guess the current implementation is wrongly assuming that access_model_add could be called after the model state has been restored, which can never be the case as with the current implementation of mesh_stack_init. I think this can be fixed in SDK version 3.2.0 in the restore_acquired_model, restore_acquired_element, restore_acquired_subscription_list functions by calling ACCESS_INTERNAL_STATE_OUTDATED_CLR in each of the functions.

I did also take a quick look a the current nRF5 Mesh SDK 4.0.0 where the above code has been refactored. I guess the issue is even worse for this version of the SDK as the model state is stored directly in the access_model_add function which definitely happens before the mesh_stack_init function could load model data from flash.

Is this a bug in the SDKs or am I doing something wrong here?

  • Hi,

    I have reached out to the developers and will update you when I get a response.

  • This is the reply from the developers:

    I guess the customer is partially right. There might be issues with internal states of flash entries till version 4.0.0. Initially, it was implemented with the ability to change models at any time. Much later we sorted out that changing models and etc (aka composition data) causes unpredictable consequences since the models require additional addresses on the new elements, etc. After that, the concept of frozen composition data has been added. After freezing the data it is not possible to add models and to change the composition data. It is still possible to have some leftovers in implementation related to the previous assumption. I do not think they impact on anything.

    Secondly, the customer is wrong about v4.0.0. " the model state is stored directly in the access_model_add".  This is not the truth. When a model is added before loading the flash data,  the flag ''refresh model in flash" is set. After a model is restored from the flash, the flag "refresh model in flash" is cleaned and nothing happens with the flash snapshot. If a model is not restored from the flash  (as it happens when device boots up for the very first time) then model data initialized in the init stage is stored to flash (i.e. flag ''refresh model in flash" fires). It is still possible to update the model composition as long as the device is in the un-provisioned state. The state of the device being provisioned or not is analogous to the `frozen` state from 3.2.0.

Related