Platform: Extend NV counter interface

This patch adds the tfm_plat_set_nv_counter() function to the NV counter
interface for supported platforms: AN521, AN519, Musca-A and Musca-B1.
To set a NV counter to a specified value can take a lot of time and
leads to early flash wear out when incrementing the counter by one.

Change-Id: I8702de54d373aff3201669540c541a24815cf09b
Signed-off-by: David Vincze <david.vincze@arm.com>
diff --git a/platform/ext/target/mps2/an519/dummy_nv_counters.c b/platform/ext/target/mps2/an519/dummy_nv_counters.c
index 6f8ea62..8eda9bd 100644
--- a/platform/ext/target/mps2/an519/dummy_nv_counters.c
+++ b/platform/ext/target/mps2/an519/dummy_nv_counters.c
@@ -1,18 +1,20 @@
 /*
- * Copyright (c) 2018, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2019, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  *
  */
 
-/* NOTE: This API should be implemented by platform vendor. For the
- * security of the secure storage system rollback protection and others, it is
- * CRITICAL to use a internal (in-die) persistent memory for multiple time
- * programabe (MTP) non-volatile counters or use a One-time Programmable (OTP)
+/* NOTE: This API should be implemented by platform vendor. For the security of
+ * the secure storage system's and the bootloader's rollback protection etc. it
+ * is CRITICAL to use a internal (in-die) persistent memory for multiple time
+ * programmable (MTP) non-volatile counters or use a One-time Programmable (OTP)
  * non-volatile counters solution.
  *
  * AN519 does not have any available MTP or OTP non-volatile counters, so a
- * software dummy implementation has been implemented in this case.
+ * software dummy implementation has been implemented in this case. The current
+ * implementation is not resistant to asynchronous power failures and should
+ * not be used in production code. It is exclusively for testing purposes.
  */
 
 #include "platform/include/tfm_plat_nv_counters.h"
@@ -123,8 +125,8 @@
     return TFM_PLAT_ERR_SUCCESS;
 }
 
-enum tfm_plat_err_t tfm_plat_increment_nv_counter(
-                                               enum tfm_nv_counter_t counter_id)
+enum tfm_plat_err_t tfm_plat_set_nv_counter(enum tfm_nv_counter_t counter_id,
+                                            uint32_t value)
 {
     int32_t  err;
     uint32_t *p_nv_counter;
@@ -141,25 +143,48 @@
     p_nv_counter = (uint32_t *)(sector_data + NV_COUNTERS_AREA_OFFSET +
                                 (counter_id * NV_COUNTER_SIZE));
 
-    if (*p_nv_counter == UINT32_MAX) {
-        return TFM_PLAT_ERR_MAX_VALUE;
-    }
+    if (value != *p_nv_counter) {
 
-    /* Next value is the current value + 1 */
-    *p_nv_counter = *p_nv_counter + 1;
+        if (value > *p_nv_counter) {
+            *p_nv_counter = value;
+        } else {
+            return TFM_PLAT_ERR_INVALID_INPUT;
+        }
 
-    /* Erase sector before write in it */
-    err = FLASH_DEV_NAME.EraseSector(TFM_NV_COUNTERS_SECTOR_ADDR);
-    if (err != ARM_DRIVER_OK) {
-        return TFM_PLAT_ERR_SYSTEM_ERR;
-    }
+        /* Erase sector before write in it */
+        err = FLASH_DEV_NAME.EraseSector(TFM_NV_COUNTERS_SECTOR_ADDR);
+        if (err != ARM_DRIVER_OK) {
+            return TFM_PLAT_ERR_SYSTEM_ERR;
+        }
 
-    /* Write in flash the in-memory block content after modification */
-    err = FLASH_DEV_NAME.ProgramData(TFM_NV_COUNTERS_SECTOR_ADDR, sector_data,
-                                     TFM_NV_COUNTERS_SECTOR_SIZE);
-    if (err != ARM_DRIVER_OK) {
-        return TFM_PLAT_ERR_SYSTEM_ERR;
+        /* Write in flash the in-memory block content after modification */
+        err = FLASH_DEV_NAME.ProgramData(TFM_NV_COUNTERS_SECTOR_ADDR,
+                                         sector_data,
+                                         TFM_NV_COUNTERS_SECTOR_SIZE);
+        if (err != ARM_DRIVER_OK) {
+            return TFM_PLAT_ERR_SYSTEM_ERR;
+        }
     }
 
     return TFM_PLAT_ERR_SUCCESS;
 }
+
+enum tfm_plat_err_t tfm_plat_increment_nv_counter(
+                                           enum tfm_nv_counter_t counter_id)
+{
+    uint32_t security_cnt;
+    enum tfm_plat_err_t err;
+
+    err = tfm_plat_read_nv_counter(counter_id,
+                                   sizeof(security_cnt),
+                                   (uint8_t *)&security_cnt);
+    if (err != TFM_PLAT_ERR_SUCCESS) {
+        return err;
+    }
+
+    if (security_cnt == UINT32_MAX) {
+        return TFM_PLAT_ERR_MAX_VALUE;
+    }
+
+    return tfm_plat_set_nv_counter(counter_id, security_cnt + 1u);
+}
diff --git a/platform/ext/target/mps2/an521/dummy_nv_counters.c b/platform/ext/target/mps2/an521/dummy_nv_counters.c
index e82cd7c..b144994 100644
--- a/platform/ext/target/mps2/an521/dummy_nv_counters.c
+++ b/platform/ext/target/mps2/an521/dummy_nv_counters.c
@@ -1,18 +1,20 @@
 /*
- * Copyright (c) 2018, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2019, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  *
  */
 
-/* NOTE: This API should be implemented by platform vendor. For the
- * security of the secure storage system rollback protection and others, it is
- * CRITICAL to use a internal (in-die) persistent memory for multiple time
- * programabe (MTP) non-volatile counters or use a One-time Programmable (OTP)
+/* NOTE: This API should be implemented by platform vendor. For the security of
+ * the secure storage system's and the bootloader's rollback protection etc. it
+ * is CRITICAL to use a internal (in-die) persistent memory for multiple time
+ * programmable (MTP) non-volatile counters or use a One-time Programmable (OTP)
  * non-volatile counters solution.
  *
  * AN521 does not have any available MTP or OTP non-volatile counters, so a
- * software dummy implementation has been implemented in this case.
+ * software dummy implementation has been implemented in this case. The current
+ * implementation is not resistant to asynchronous power failures and should
+ * not be used in production code. It is exclusively for testing purposes.
  */
 
 #include "platform/include/tfm_plat_nv_counters.h"
@@ -123,8 +125,8 @@
     return TFM_PLAT_ERR_SUCCESS;
 }
 
-enum tfm_plat_err_t tfm_plat_increment_nv_counter(
-                                               enum tfm_nv_counter_t counter_id)
+enum tfm_plat_err_t tfm_plat_set_nv_counter(enum tfm_nv_counter_t counter_id,
+                                            uint32_t value)
 {
     int32_t  err;
     uint32_t *p_nv_counter;
@@ -141,25 +143,48 @@
     p_nv_counter = (uint32_t *)(sector_data + NV_COUNTERS_AREA_OFFSET +
                                 (counter_id * NV_COUNTER_SIZE));
 
-    if (*p_nv_counter == UINT32_MAX) {
-        return TFM_PLAT_ERR_MAX_VALUE;
-    }
+    if (value != *p_nv_counter) {
 
-    /* Next value is the current value + 1 */
-    *p_nv_counter = *p_nv_counter + 1;
+        if (value > *p_nv_counter) {
+            *p_nv_counter = value;
+        } else {
+            return TFM_PLAT_ERR_INVALID_INPUT;
+        }
 
-    /* Erase sector before write in it */
-    err = FLASH_DEV_NAME.EraseSector(TFM_NV_COUNTERS_SECTOR_ADDR);
-    if (err != ARM_DRIVER_OK) {
-        return TFM_PLAT_ERR_SYSTEM_ERR;
-    }
+        /* Erase sector before write in it */
+        err = FLASH_DEV_NAME.EraseSector(TFM_NV_COUNTERS_SECTOR_ADDR);
+        if (err != ARM_DRIVER_OK) {
+            return TFM_PLAT_ERR_SYSTEM_ERR;
+        }
 
-    /* Write in flash the in-memory block content after modification */
-    err = FLASH_DEV_NAME.ProgramData(TFM_NV_COUNTERS_SECTOR_ADDR, sector_data,
-                                     TFM_NV_COUNTERS_SECTOR_SIZE);
-    if (err != ARM_DRIVER_OK) {
-        return TFM_PLAT_ERR_SYSTEM_ERR;
+        /* Write in flash the in-memory block content after modification */
+        err = FLASH_DEV_NAME.ProgramData(TFM_NV_COUNTERS_SECTOR_ADDR,
+                                         sector_data,
+                                         TFM_NV_COUNTERS_SECTOR_SIZE);
+        if (err != ARM_DRIVER_OK) {
+            return TFM_PLAT_ERR_SYSTEM_ERR;
+        }
     }
 
     return TFM_PLAT_ERR_SUCCESS;
 }
+
+enum tfm_plat_err_t tfm_plat_increment_nv_counter(
+                                           enum tfm_nv_counter_t counter_id)
+{
+    uint32_t security_cnt;
+    enum tfm_plat_err_t err;
+
+    err = tfm_plat_read_nv_counter(counter_id,
+                                   sizeof(security_cnt),
+                                   (uint8_t *)&security_cnt);
+    if (err != TFM_PLAT_ERR_SUCCESS) {
+        return err;
+    }
+
+    if (security_cnt == UINT32_MAX) {
+        return TFM_PLAT_ERR_MAX_VALUE;
+    }
+
+    return tfm_plat_set_nv_counter(counter_id, security_cnt + 1u);
+}
diff --git a/platform/ext/target/musca_a/dummy_nv_counters.c b/platform/ext/target/musca_a/dummy_nv_counters.c
index 96c6a23..2703582 100644
--- a/platform/ext/target/musca_a/dummy_nv_counters.c
+++ b/platform/ext/target/musca_a/dummy_nv_counters.c
@@ -5,14 +5,16 @@
  *
  */
 
-/* NOTE: This API should be implemented by platform vendor. For the
- * security of the secure storage system rollback protection and others, it is
- * CRITICAL to use a internal (in-die) persistent memory for multiple time
- * programabe (MTP) non-volatile counters or use a One-time Programmable (OTP)
+/* NOTE: This API should be implemented by platform vendor. For the security of
+ * the secure storage system's and the bootloader's rollback protection etc. it
+ * is CRITICAL to use a internal (in-die) persistent memory for multiple time
+ * programmable (MTP) non-volatile counters or use a One-time Programmable (OTP)
  * non-volatile counters solution.
  *
  * Musca-A does not have any available MTP or OTP non-volatile counters, so a
- * software dummy implementation has been implemented in this case.
+ * software dummy implementation has been implemented in this case. The current
+ * implementation is not resistant to asynchronous power failures and should
+ * not be used in production code. It is exclusively for testing purposes.
  */
 
 #include "platform/include/tfm_plat_nv_counters.h"
@@ -123,8 +125,8 @@
     return TFM_PLAT_ERR_SUCCESS;
 }
 
-enum tfm_plat_err_t tfm_plat_increment_nv_counter(
-                                               enum tfm_nv_counter_t counter_id)
+enum tfm_plat_err_t tfm_plat_set_nv_counter(enum tfm_nv_counter_t counter_id,
+                                            uint32_t value)
 {
     int32_t  err;
     uint32_t *p_nv_counter;
@@ -141,25 +143,48 @@
     p_nv_counter = (uint32_t *)(sector_data + NV_COUNTERS_AREA_OFFSET +
                                 (counter_id * NV_COUNTER_SIZE));
 
-    if (*p_nv_counter == UINT32_MAX) {
-        return TFM_PLAT_ERR_MAX_VALUE;
-    }
+    if (value != *p_nv_counter) {
 
-    /* Next value is the current value + 1 */
-    *p_nv_counter = *p_nv_counter + 1;
+        if (value > *p_nv_counter) {
+            *p_nv_counter = value;
+        } else {
+            return TFM_PLAT_ERR_INVALID_INPUT;
+        }
 
-    /* Erase sector before write in it */
-    err = FLASH_DEV_NAME.EraseSector(TFM_NV_COUNTERS_SECTOR_ADDR);
-    if (err != ARM_DRIVER_OK) {
-        return TFM_PLAT_ERR_SYSTEM_ERR;
-    }
+        /* Erase sector before write in it */
+        err = FLASH_DEV_NAME.EraseSector(TFM_NV_COUNTERS_SECTOR_ADDR);
+        if (err != ARM_DRIVER_OK) {
+            return TFM_PLAT_ERR_SYSTEM_ERR;
+        }
 
-    /* Write in flash the in-memory block content after modification */
-    err = FLASH_DEV_NAME.ProgramData(TFM_NV_COUNTERS_SECTOR_ADDR, sector_data,
-                                     TFM_NV_COUNTERS_SECTOR_SIZE);
-    if (err != ARM_DRIVER_OK) {
-        return TFM_PLAT_ERR_SYSTEM_ERR;
+        /* Write in flash the in-memory block content after modification */
+        err = FLASH_DEV_NAME.ProgramData(TFM_NV_COUNTERS_SECTOR_ADDR,
+                                         sector_data,
+                                         TFM_NV_COUNTERS_SECTOR_SIZE);
+        if (err != ARM_DRIVER_OK) {
+            return TFM_PLAT_ERR_SYSTEM_ERR;
+        }
     }
 
     return TFM_PLAT_ERR_SUCCESS;
 }
+
+enum tfm_plat_err_t tfm_plat_increment_nv_counter(
+                                           enum tfm_nv_counter_t counter_id)
+{
+    uint32_t security_cnt;
+    enum tfm_plat_err_t err;
+
+    err = tfm_plat_read_nv_counter(counter_id,
+                                   sizeof(security_cnt),
+                                   (uint8_t *)&security_cnt);
+    if (err != TFM_PLAT_ERR_SUCCESS) {
+        return err;
+    }
+
+    if (security_cnt == UINT32_MAX) {
+        return TFM_PLAT_ERR_MAX_VALUE;
+    }
+
+    return tfm_plat_set_nv_counter(counter_id, security_cnt + 1u);
+}
diff --git a/platform/ext/target/musca_b1/dummy_nv_counters.c b/platform/ext/target/musca_b1/dummy_nv_counters.c
index 17c1c4f..a935959 100644
--- a/platform/ext/target/musca_b1/dummy_nv_counters.c
+++ b/platform/ext/target/musca_b1/dummy_nv_counters.c
@@ -1,15 +1,19 @@
 /*
- * Copyright (c) 2018, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2019, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  *
  */
 
-/* NOTE: This API should be implemented by platform vendor. For the
- * security of the secure storage system rollback protection and others, it is
- * CRITICAL to use a internal (in-die) persistent memory for multiple time
- * programabe (MTP) non-volatile counters or use a One-time Programmable (OTP)
+/* NOTE: This API should be implemented by platform vendor. For the security of
+ * the secure storage system's and the bootloader's rollback protection etc. it
+ * is CRITICAL to use a internal (in-die) persistent memory for multiple time
+ * programmable (MTP) non-volatile counters or use a One-time Programmable (OTP)
  * non-volatile counters solution.
+ *
+ * The current software dummy implementation is not resistant to asynchronous
+ * power failures and should not be used in production code. It is exclusively
+ * for testing purposes.
  */
 
 #include "platform/include/tfm_plat_nv_counters.h"
@@ -120,8 +124,8 @@
     return TFM_PLAT_ERR_SUCCESS;
 }
 
-enum tfm_plat_err_t tfm_plat_increment_nv_counter(
-                                               enum tfm_nv_counter_t counter_id)
+enum tfm_plat_err_t tfm_plat_set_nv_counter(enum tfm_nv_counter_t counter_id,
+                                            uint32_t value)
 {
     int32_t  err;
     uint32_t *p_nv_counter;
@@ -138,25 +142,48 @@
     p_nv_counter = (uint32_t *)(sector_data + NV_COUNTERS_AREA_OFFSET +
                                 (counter_id * NV_COUNTER_SIZE));
 
-    if (*p_nv_counter == UINT32_MAX) {
-        return TFM_PLAT_ERR_MAX_VALUE;
-    }
+    if (value != *p_nv_counter) {
 
-    /* Next value is the current value + 1 */
-    *p_nv_counter = *p_nv_counter + 1;
+        if (value > *p_nv_counter) {
+            *p_nv_counter = value;
+        } else {
+            return TFM_PLAT_ERR_INVALID_INPUT;
+        }
 
-    /* Erase sector before write in it */
-    err = FLASH_DEV_NAME.EraseSector(TFM_NV_COUNTERS_SECTOR_ADDR);
-    if (err != ARM_DRIVER_OK) {
-        return TFM_PLAT_ERR_SYSTEM_ERR;
-    }
+        /* Erase sector before write in it */
+        err = FLASH_DEV_NAME.EraseSector(TFM_NV_COUNTERS_SECTOR_ADDR);
+        if (err != ARM_DRIVER_OK) {
+            return TFM_PLAT_ERR_SYSTEM_ERR;
+        }
 
-    /* Write in flash the in-memory block content after modification */
-    err = FLASH_DEV_NAME.ProgramData(TFM_NV_COUNTERS_SECTOR_ADDR, sector_data,
-                                     TFM_NV_COUNTERS_SECTOR_SIZE);
-    if (err != ARM_DRIVER_OK) {
-        return TFM_PLAT_ERR_SYSTEM_ERR;
+        /* Write in flash the in-memory block content after modification */
+        err = FLASH_DEV_NAME.ProgramData(TFM_NV_COUNTERS_SECTOR_ADDR,
+                                         sector_data,
+                                         TFM_NV_COUNTERS_SECTOR_SIZE);
+        if (err != ARM_DRIVER_OK) {
+            return TFM_PLAT_ERR_SYSTEM_ERR;
+        }
     }
 
     return TFM_PLAT_ERR_SUCCESS;
 }
+
+enum tfm_plat_err_t tfm_plat_increment_nv_counter(
+                                           enum tfm_nv_counter_t counter_id)
+{
+    uint32_t security_cnt;
+    enum tfm_plat_err_t err;
+
+    err = tfm_plat_read_nv_counter(counter_id,
+                                   sizeof(security_cnt),
+                                   (uint8_t *)&security_cnt);
+    if (err != TFM_PLAT_ERR_SUCCESS) {
+        return err;
+    }
+
+    if (security_cnt == UINT32_MAX) {
+        return TFM_PLAT_ERR_MAX_VALUE;
+    }
+
+    return tfm_plat_set_nv_counter(counter_id, security_cnt + 1u);
+}
diff --git a/platform/include/tfm_plat_defs.h b/platform/include/tfm_plat_defs.h
index f0b4297..ffe61f6 100644
--- a/platform/include/tfm_plat_defs.h
+++ b/platform/include/tfm_plat_defs.h
@@ -19,6 +19,8 @@
     TFM_PLAT_ERR_SUCCESS = 0,
     TFM_PLAT_ERR_SYSTEM_ERR,
     TFM_PLAT_ERR_MAX_VALUE,
+    TFM_PLAT_ERR_INVALID_INPUT,
+    TFM_PLAT_ERR_UNSUPPORTED,
     /* Following entry is only to ensure the error code of int size */
     TFM_PLAT_ERR_FORCE_INT_SIZE = INT_MAX
 };
diff --git a/platform/include/tfm_plat_nv_counters.h b/platform/include/tfm_plat_nv_counters.h
index d79c235..9b86112 100644
--- a/platform/include/tfm_plat_nv_counters.h
+++ b/platform/include/tfm_plat_nv_counters.h
@@ -13,6 +13,14 @@
  *
  * \note The interfaces defined in this file must be implemented for each
  *       SoC.
+ * \note The interface must be implemented in a fail-safe way that is
+ *       resistant to asynchronous power failures or it can use hardware
+ *       counters that have this capability, if supported by the platform.
+ *       When a counter incrementation was interrupted it must be able to
+ *       continue the incrementation process or recover the previous consistent
+ *       status of the counters. If the counters have reached a stable status
+ *       (every counter incrementation operation has finished), from that point
+ *       their value cannot decrease due to any kind of power failure.
  */
 
 #include <stdint.h>
@@ -64,6 +72,31 @@
 enum tfm_plat_err_t tfm_plat_increment_nv_counter(
                                               enum tfm_nv_counter_t counter_id);
 
+/**
+ * \brief Sets the given non-volatile (NV) counter to the specified value.
+ *
+ * \param[in] counter_id  NV counter ID.
+ * \param[in] value       New value of the NV counter. The maximum value that
+ *                        can be set depends on the constraints of the
+ *                        underlying implementation, but it always must be
+ *                        greater than or equal to the current NV counter value.
+ *
+ * \retval TFM_PLAT_ERR_SUCCESS         The NV counter is set successfully
+ * \retval TFM_PLAT_ERR_INVALID_INPUT   The new value is less than the current
+ *                                      counter value
+ * \retval TFM_PLAT_ERR_MAX_VALUE       The new value is greater than the
+ *                                      maximum value of the NV counter
+ * \retval TFM_PLAT_ERR_UNSUPPORTED     The function is not implemented for
+ *                                      the given platform or the new value is
+ *                                      not representable on the underlying
+ *                                      counter implementation
+ * \retval TFM_PLAT_ERR_SYSTEM_ERR      An unspecified error occurred
+ *                                      (none of the other standard error codes
+ *                                      are applicable)
+ */
+enum tfm_plat_err_t tfm_plat_set_nv_counter(enum tfm_nv_counter_t counter_id,
+                                            uint32_t value);
+
 #ifdef __cplusplus
 }
 #endif