core/state_manager/state_manager.c (358 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include <stddef.h> #include <stdlib.h> #include <string.h> #include "state_logging.h" #include "state_manager.h" #include "flash/flash_common.h" #include "flash/flash_util.h" /* Bitmasks for settings in non-volatile memory. */ #define MULTI_BYTE_STATE (1U << 7) #define SINGLE_BYTE_STATE (1U << 6) /* Bitmasks for settings in volatile memory. */ #define SECTOR_2_BLANK (1U << 7) #define SECTOR_1_BLANK (1U << 6) /** * Initialize state stored using a single byte. * * @param manager The state manager to initialize. * @param state_flash The flash that contains the non-volatile state information. * @param store_addr The starting address for state storage. * @param sector_size The sector size of the flash device. * @param offset Output for the offset in storage where the latest state is stored. * * @return 0 if the state was successfully initialized or an error code. */ static int state_manager_init_single_byte_state (struct state_manager *manager, const struct flash *state_flash, uint32_t store_addr, uint32_t sector_size, int *offset) { uint8_t nv_state = 0xff; int status; /* Find the latest settings by finding the first byte that isn't blank. If the first byte is * blank, jump straight to the second sector to see if the settings are there. */ *offset = -1; do { if (*offset == 0) { *offset += sector_size - 1; } do { manager->nv_state = 0xff00 | nv_state; (*offset)++; if (*offset < (int) (sector_size * 2)) { status = state_flash->read (state_flash, store_addr + *offset, &nv_state, 1); if (status != 0) { return status; } } } while ((nv_state != 0xff) && (*offset < (int) (sector_size * 2))); } while (*offset == 0); manager->nv_state |= MULTI_BYTE_STATE; return 0; } /** * Read the state information from the stored state entry. * * @param entry The stored state entry to parse. * @param error Optional output to indicate that there is a bit error in the entry. * @param refresh Optional output to indicate that there is an error that requires a state refresh. * This will only ever get set, never cleared. * * @return The state information. */ static uint16_t state_manager_read_state_bits (uint16_t *entry, bool *error, bool *refresh) { int i; uint16_t nv_state = 0; uint16_t bit0; uint16_t bit1; uint16_t bit2; if (error) { *error = false; } for (i = 0; i < 16; i++) { bit0 = entry[0] & (1U << i); bit1 = entry[1] & (1U << i); bit2 = entry[2] & (1U << i); if ((bit0 == bit1) && (bit0 == bit2)) { nv_state |= bit0; } else { if (error) { *error = true; } /* If there is a bit error on a marker for a valid entry, always leave it cleared. */ if ((i != 6) && (i != 7)) { if ((bit0 == bit1) || (bit0 == bit2)) { nv_state |= bit0; } else { nv_state |= bit1; } } else if ((i == 6) && refresh) { /* Bit errors on the entry valid marker always trigger a refresh. */ *refresh = true; } } } /* If the last word has some data but the entry reads as blank, assume this is a valid entry * with lots of bit errors. */ if ((nv_state == 0xffff) && (entry[3] != 0xffff)) { nv_state &= ~SINGLE_BYTE_STATE; if (refresh) { *refresh = true; } } /* If both format bits are cleared, assume a multi-byte format with bit errors. One bit always * will be set, and they are mutually exclusive. */ if (!(nv_state & (SINGLE_BYTE_STATE | MULTI_BYTE_STATE))) { nv_state |= MULTI_BYTE_STATE; } return nv_state; } /** * Initialize state stored using multiple bytes with redundancy. * * @param manager The state manager to initialize. * @param state_flash The flash that contains the non-volatile state information. * @param store_addr The starting address for state storage. * @param sector_size The sector size of the flash device. * @param offset Output for the offset in storage where the latest state is stored. * @param bit_error Output indicating if the latest state contains a bit error. * @param refresh_state Output indicating if there is an error requiring the state to be refreshed. * * @return 0 if the state was successfully initialized or an error code. */ static int state_manager_init_multi_byte_state (struct state_manager *manager, const struct flash *state_flash, uint32_t store_addr, uint32_t sector_size, int *offset, bool *bit_error, bool *refresh_state) { uint16_t stored[4]; uint16_t nv_state = 0xffff; bool error = false; int status; /* Find the latest settings by finding the first entry that isn't blank. If the first entry is * blank, jump straight to the second sector to see if the settings are there. */ *offset = -8; *refresh_state = false; do { if (*offset == 0) { *offset += sector_size - 8; } do { manager->nv_state = nv_state; *bit_error = error; (*offset) += 8; if (*offset < (int) (sector_size * 2)) { status = state_flash->read (state_flash, store_addr + *offset, (uint8_t*) stored, sizeof (stored)); if (status != 0) { return status; } nv_state = state_manager_read_state_bits (stored, &error, refresh_state); } } while ((nv_state != 0xffff) && !(nv_state & SINGLE_BYTE_STATE) && (*offset < (int) (sector_size * 2))); } while (*offset == 0); return 0; } /** * Set the write offset to have the next state stored the beginning of the unused sector. * * @param manager The state manager to update. * @param sector_size The flash sector size. */ static void state_manager_set_next_sector_write_offset (struct state_manager *manager, uint32_t sector_size) { if (manager->store_addr < (manager->base_addr + sector_size)) { manager->store_addr = manager->base_addr + sector_size - 8; } else { manager->store_addr = manager->base_addr + (sector_size * 2) - 8; } } /** * Initialize the manager for state information. * * @param manager The state manager to initialize. * @param state_flash The flash that contains the non-volatile state information. * @param store_addr The starting address for state storage. The state storage uses two contiguous * flash sectors. The start address must be aligned to the start of a flash sector. * * @return 0 if the state manager was successfully initialized or an error code. */ int state_manager_init (struct state_manager *manager, const struct flash *state_flash, uint32_t store_addr) { int offset; uint32_t sector_size; uint16_t sector1[4]; uint16_t state1; uint16_t sector2[4]; uint16_t state2; bool needs_update = false; bool bit_error = false; int status; if ((manager == NULL) || (state_flash == NULL)) { return STATE_MANAGER_INVALID_ARGUMENT; } status = state_flash->get_sector_size (state_flash, &sector_size); if (status != 0) { return status; } if (FLASH_REGION_BASE (store_addr, sector_size) != store_addr) { return STATE_MANAGER_NOT_SECTOR_ALIGNED; } memset (manager, 0, sizeof (struct state_manager)); status = state_flash->read (state_flash, store_addr, (uint8_t*) sector1, sizeof (sector1)); if (status != 0) { return status; } status = state_flash->read (state_flash, store_addr + sector_size, (uint8_t*) sector2, sizeof (sector2)); if (status != 0) { return status; } state1 = state_manager_read_state_bits (sector1, NULL, NULL); state2 = state_manager_read_state_bits (sector2, NULL, NULL); manager->nv_state = 0xffff; if ((state1 != 0xffff) || (state2 != 0xffff)) { if (!(state1 & SINGLE_BYTE_STATE) || !(state2 & SINGLE_BYTE_STATE)) { status = state_manager_init_multi_byte_state (manager, state_flash, store_addr, sector_size, &offset, &bit_error, &needs_update); } else { status = state_manager_init_single_byte_state (manager, state_flash, store_addr, sector_size, &offset); needs_update = true; } } else { status = flash_sector_erase_region_and_verify (state_flash, store_addr, sector_size); manager->volatile_state |= SECTOR_1_BLANK; offset = sector_size * 2; } if (status != 0) { return status; } manager->nv_store = state_flash; manager->base_addr = store_addr; manager->store_addr = store_addr + offset - 8; manager->last_nv_stored = manager->nv_state; if (needs_update || bit_error) { /* If the state is stored in the old format or the current state information has corruption, * force the state to be stored on flash at the next request. */ manager->last_nv_stored = 0xffff; if (needs_update) { /* Make sure the address is entry aligned. */ if (offset & 0x7) { manager->store_addr += (8 - (offset & 0x7)); } state_manager_set_next_sector_write_offset (manager, sector_size); } } status = platform_mutex_init (&manager->state_lock); if (status != 0) { return status; } status = platform_mutex_init (&manager->store_lock); if (status != 0) { platform_mutex_free (&manager->state_lock); return status; } return 0; } /** * Release the resources used by the host state manager. * * @param manager The state manager to release. */ void state_manager_release (struct state_manager *manager) { if (manager != NULL) { platform_mutex_free (&manager->state_lock); platform_mutex_free (&manager->store_lock); } } /** * Prevent calls to store the non-volatile state from executing. Calls will remain mutex blocked * until they are once again allowed. * * If there is a call to store the non-volatile state in progress, this will not return until that * call has completed. * * Calling this function has the same semantics as a mutex. Meaning, calling this twice to block * stores without calling to block in between will cause deadlock. * * @param manager The manager whose state storage should be prevented or allowed. * @param block True to prevent state storage or false to allow it. */ void state_manager_block_non_volatile_state_storage (struct state_manager *manager, bool block) { if (manager != NULL) { if (block) { platform_mutex_lock (&manager->store_lock); } else { platform_mutex_unlock (&manager->store_lock); } } } /** * Store the current non-volatile state to flash. * * It is expected that this function would be called in the context of a background task that will * periodically store the non-volatile state. This call could result in the need to erase flash, so * it could take an extended time for the operation to complete. * * @param manager The manager whose state should be stored. * * @return 0 if the non-volatile state was successfully stored or an error code. */ int state_manager_store_non_volatile_state (struct state_manager *manager) { int status = 0; int erase_status; uint16_t store_state; uint16_t nv_state[4]; uint32_t next_addr; uint32_t sector_size; uint16_t in_flash; bool bit_error = false; bool refresh = false; if (manager == NULL) { return STATE_MANAGER_INVALID_ARGUMENT; } status = manager->nv_store->get_sector_size (manager->nv_store, &sector_size); if (status != 0) { return status; } platform_mutex_lock (&manager->store_lock); platform_mutex_lock (&manager->state_lock); store_state = manager->nv_state & ~SINGLE_BYTE_STATE; store_state |= MULTI_BYTE_STATE; platform_mutex_unlock (&manager->state_lock); /* If our current state hasn't changed from what is on flash, verify the flash contents and * refresh as necessary. */ if (store_state == manager->last_nv_stored) { status = manager->nv_store->read (manager->nv_store, manager->store_addr, (uint8_t*) nv_state, sizeof (nv_state)); if (status == 0) { in_flash = state_manager_read_state_bits (nv_state, &bit_error, &refresh); if ((in_flash != store_state) || bit_error) { /* The data in flash is bad, so force the state to be rewritten. */ manager->last_nv_stored = 0xffff; if (refresh) { state_manager_set_next_sector_write_offset (manager, sector_size); } } } } /* If our current state is different from that stored on flash, write it to flash. */ if (store_state != manager->last_nv_stored) { next_addr = manager->store_addr + 8; if (next_addr == (manager->base_addr + (sector_size * 2))) { next_addr = manager->base_addr; } nv_state[0] = store_state; nv_state[1] = store_state; nv_state[2] = store_state; nv_state[3] = 0; /* Make sure we are writing to a blank sector. * * If we are trying to write the last entry in the current sector, make sure the next sector * is erased. Otherwise, we will not be able to correctly determine the last state stored * by looking for blank flash during initialization. */ if ((next_addr == manager->base_addr) && !(manager->volatile_state & SECTOR_1_BLANK)) { status = STATE_MANAGER_NOT_BLANK; } else if ((next_addr == (manager->base_addr + sector_size)) && !(manager->volatile_state & SECTOR_2_BLANK)) { status = STATE_MANAGER_NOT_BLANK; } else if ((next_addr == (manager->base_addr + (sector_size * 2) - 8)) && !(manager->volatile_state & SECTOR_1_BLANK)) { status = STATE_MANAGER_NOT_BLANK; } else if ((next_addr == (manager->base_addr + sector_size - 8)) && !(manager->volatile_state & SECTOR_2_BLANK)) { status = STATE_MANAGER_NOT_BLANK; } if (status == 0) { status = manager->nv_store->write (manager->nv_store, next_addr, (uint8_t*) nv_state, sizeof (nv_state)); if (ROT_IS_ERROR (status)) { platform_mutex_unlock (&manager->store_lock); return status; } if (status == sizeof (nv_state)) { status = 0; manager->last_nv_stored = store_state; } else { /* We handle this scenario, but only minimally. This is not really possible given * the alignment of data. */ status = STATE_MANAGER_INCOMPLETE_WRITE; } manager->store_addr = next_addr; } } /* Always make sure the unused sector is erased so it is ready to be written to when needed. * A failure to erase is not a reported error since the data was successfully stored. */ if (manager->store_addr < (manager->base_addr + sector_size)) { if (manager->volatile_state & SECTOR_1_BLANK) { manager->volatile_state &= ~SECTOR_1_BLANK; } if (!(manager->volatile_state & SECTOR_2_BLANK)) { erase_status = flash_sector_erase_region_and_verify (manager->nv_store, manager->base_addr + sector_size, sector_size); if (erase_status == 0) { manager->volatile_state |= SECTOR_2_BLANK; } else { debug_log_create_entry (DEBUG_LOG_SEVERITY_WARNING, DEBUG_LOG_COMPONENT_STATE_MGR, STATE_LOGGING_ERASE_FAIL, manager->base_addr + sector_size, status); } } } else { if (manager->volatile_state & SECTOR_2_BLANK) { manager->volatile_state &= ~SECTOR_2_BLANK; } if (!(manager->volatile_state & SECTOR_1_BLANK)) { erase_status = flash_sector_erase_region_and_verify (manager->nv_store, manager->base_addr, sector_size); if (erase_status == 0) { manager->volatile_state |= SECTOR_1_BLANK; } else { debug_log_create_entry (DEBUG_LOG_SEVERITY_WARNING, DEBUG_LOG_COMPONENT_STATE_MGR, STATE_LOGGING_ERASE_FAIL, manager->base_addr, status); } } } platform_mutex_unlock (&manager->store_lock); return status; } /** * Save the setting for the manifest region that contains the active manifest. * This setting will be stored in non-volatile memory on the next call to store state. * * @param manager The state manager to update. * @param active The manifest region to save as the active region. * @param bit The bit used to store this information. * * @return 0 if the setting was saved or an an error code if the setting was invalid. */ int state_manager_save_active_manifest (struct state_manager *manager, enum manifest_region active, uint8_t bit) { int status = 0; if (manager == NULL) { return STATE_MANAGER_INVALID_ARGUMENT; } platform_mutex_lock (&manager->state_lock); switch (active) { case MANIFEST_REGION_1: manager->nv_state = manager->nv_state | bit; break; case MANIFEST_REGION_2: manager->nv_state = manager->nv_state & ~bit; break; default: status = STATE_MANAGER_INVALID_ARGUMENT; break; } platform_mutex_unlock (&manager->state_lock); return status; } /** * Get the current setting for the active manifest region. * * @param manager The state manager to query. * @param bit The bit used to store this information. * * @return The active manifest region. */ enum manifest_region state_manager_get_active_manifest (struct state_manager *manager, uint8_t bit) { if (manager == NULL) { return MANIFEST_REGION_1; } return (manager->nv_state & bit) ? MANIFEST_REGION_1 : MANIFEST_REGION_2; }