/* Copyright 2016 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef __CROS_EC_NVMEM_UTILS_H
#define __CROS_EC_NVMEM_UTILS_H

#include "crypto_api.h"

/*
 * In order to provide maximum robustness for NvMem operations, the NvMem space
 * is divided into two equal sized partitions. A partition contains a tag
 * and a buffer for each NvMem user.
 *
 *     NvMem Partiion
 *     ------------------------------------------------------------------------
 *     |36 byte tag | User Buffer 0 | User Buffer 1 | .... |  User Buffer N-1 |
 *     ------------------------------------------------------------------------
 *
 *     Physical Block Tag details
 *     ------------------------------------------------------------------------
 *     |      sha       |      padding     |  version  | generation | reserved |
 *     -------------------------------------------------------------------------
 *         sha        -> 16 bytes of sha1 digest
 *         padding    -> 16 bytes for future extensions
 *         version    -> nvmem layout version, currently at 0
 *         generation -> 1 byte generation number (0 - 0xfe)
 *         reserved   -> 2 bytes
 *
 * At initialization time, each partition is scanned to see if it has a good sha
 * entry. One of the two partitions being valid is a supported condition. If
 * neither partiion is valid a new partition is created with generation set to
 * zero.
 *
 * Note that the NvMem partitions can be placed anywhere in flash space, but
 * must be equal in total size. A table is used by the NvMem module to get the
 * correct base address for each partition.
 *
 * A generation number is used to distinguish between two valid partitions with
 * the newsest generation number (in a circular sense) marking the correct
 * partition to use. The parition number 0/1 is tracked via a static
 * variable. When the NvMem contents need to be updated, the flash erase/write
 * of the updated partition will use the inactive partition space in NvMem. This
 * way if there is a critical failure (i.e. loss of power) during the erase or
 * write operation, then the contents of the active partition prior the most
 * recent writes will still be preserved.
 *
 * The following CONFIG_FLASH_NVMEM_ defines are required for this module:
 *    CONFIG_FLASH_NVMEM -> enable/disable the module
 *    CONFIG_FLASH_NVMEM_OFFSET_(A|B) -> offset to start of each partition
 *    CONFIG_FLASH_NVMEM_BASE_(A|B) -> address of start of each partition
 *
 * The board.h file must define a macro or enum named NVMEM_NUM_USERS.
 * The board.c file must implement:
 *    nvmem_user_sizes[] -> array of user buffer lengths
 * The chip must provide
 *    app_compute_hash() -> function used to compute 16 byte sha (or equivalent)
 *
 * Note that total length of user buffers must satisfy the following:
 *   sum(user sizes) <= (NVMEM_PARTITION_SIZE) - sizeof(struct nvmem_tag)
 */

/* NvMem user buffer length table */
extern uint32_t nvmem_user_sizes[NVMEM_NUM_USERS];

#define NVMEM_NUM_PARTITIONS 2
#define NVMEM_SHA_SIZE CIPHER_SALT_SIZE
#define NVMEM_GENERATION_BITS 8
#define NVMEM_GENERATION_MASK ((1 << NVMEM_GENERATION_BITS) - 1)
#define NVMEM_PADDING_SIZE 16
#define NVMEM_LAYOUT_VERSION 0

/* Struct for NV block tag */
struct nvmem_tag {
	uint8_t sha[NVMEM_SHA_SIZE];
	uint8_t padding[NVMEM_PADDING_SIZE];
	uint8_t layout_version;
	uint8_t generation;
	uint8_t reserved[2];
};

/* Structure MvMem Partition */
struct nvmem_partition {
	struct nvmem_tag tag;
	uint8_t buffer[NVMEM_PARTITION_SIZE -
		       sizeof(struct nvmem_tag)];
};

/**
 * Initialize NVMem translation table and state variables
 *
 * @return EC_SUCCESS if a valid translation table is constructed, else
 *         error code.
 */
int nvmem_init(void);

/**
 * Get Nvmem internal error state
 *
 * @return nvmem_error_state variable.
 */
int nvmem_get_error_state(void);

/**
 * Compare 'size' amount of bytes in NvMem
 *
 * @param offset: Offset (in bytes) into NVmem logical space
 * @param size: Number of bytes to compare
 * @param data: Pointer to data to be compared with
 * @param user: Data section within NvMem space
 * @return 0 if the data is same, non-zero if data is different
 */
int nvmem_is_different(uint32_t offset, uint32_t size,
		       void *data, enum nvmem_users user);

/**
 * Read 'size' amount of bytes from NvMem
 *
 * @param startOffset: Offset (in bytes) into NVmem logical space
 * @param size: Number of bytes to read
 * @param data: Pointer to destination buffer
 * @param user: Data section within NvMem space
 * @return EC_ERROR_OVERFLOW (non-zero) if the read operation would exceed the
 *         buffer length of the given user, otherwise EC_SUCCESS.
 */
int nvmem_read(uint32_t startOffset, uint32_t size,
		void *data, enum nvmem_users user);

/**
 * Write 'size' amount of bytes to NvMem
 *
 * Calling this function will wait for the mutex, then lock it until
 * nvmem_commit() is invoked.
 *
 * @param startOffset: Offset (in bytes) into NVmem logical space
 * @param size: Number of bytes to write
 * @param data: Pointer to source buffer
 * @param user: Data section within NvMem space
 * @return EC_ERROR_OVERFLOW if write exceeds buffer length
 *         EC_ERROR_TIMEOUT if nvmem cache buffer is not available
 *         EC_SUCCESS if no errors.
 */
int nvmem_write(uint32_t startOffset, uint32_t size,
		 void *data, enum nvmem_users user);

/**
 * Move 'size' amount of bytes within NvMem
 *
 * Calling this function will wait for the mutex, then lock it until
 * nvmem_commit() is invoked.
 *
 * @param src_offset: source offset within NvMem logical space
 * @param dest_offset: destination offset within NvMem logical space
 * @param size: Number of bytes to move
 * @param user: Data section within NvMem space
 * @return EC_ERROR_OVERFLOW if write exceeds buffer length
 *         EC_ERROR_TIMEOUT if nvmem cache buffer is not available
 *         EC_SUCCESS if no errors.
 */
int nvmem_move(uint32_t src_offset, uint32_t dest_offset, uint32_t size,
	       enum nvmem_users user);
/**
 * Commit all previous NvMem writes to flash
 *
 * @return EC_SUCCESS if flash erase/operations are successful.

 *         EC_ERROR_OVERFLOW in case the mutex is not locked when this
 *                           function is called
 *         EC_ERROR_INVAL    if task trying to commit is not the one
 *                           holding the mutex
 *         EC_ERROR_UNKNOWN  in other error cases
 */
int nvmem_commit(void);

/*
 * Clear out a user's data across all partitions.
 *
 * @param user:   The user who's data should be cleared.
 * @return        EC_SUCCESS if the user's data across all partitions was
 *                cleared.  Error othrwise.
 */
int nvmem_erase_user_data(enum nvmem_users user);

/*
 * Temporarily stopping NVMEM commits could be beneficial. One use case is
 * when TPM operations need to be sped up.
 *
 * Calling this function will wait for the mutex, then lock it until
 * nvmem_commit() is invoked.
 *
 * Both below functions should be called from the same task.
 */
void nvmem_disable_commits(void);

/*
 * Only the task holding the mutex is allowed to enable commits.
 *
 * @return error if this task does not hold the lock or commit
 *         fails, EC_SUCCESS otherwise.
 */
int nvmem_enable_commits(void);

#endif /* __CROS_EC_NVMEM_UTILS_H */
