blob: fb39d1ed9482097306ed0b25dcd5c4652d3e507e [file] [log] [blame]
#pragma once
#include <hei_bit_string.hpp>
#include <register/hei_register.hpp>
#include <util/hei_includes.hpp>
namespace libhei
{
/**
* @brief An abstract class containing information (e.g. address, type, length,
* etc.) for an actual hardware register.
*
* Hardware access:
*
* Actual hardware access is defined by the user application via the user
* interface APIs. In order to tell the user application which chip to target,
* the user application will give the isolator a list of pointers to its
* objects. They will then be passed into the public functions of this class
* and eventually given back to the user application when hardware access is
* needed.
*
* Register cache:
*
* In order to save memory space, each instance of this class does not store
* the contents of the target hardware register. Instead, that data is stored
* in a register cache, which is a static variable defined in this class. This
* allows us to store only what we need. The cache can also be thought of as a
* snapshot of the registers at the time of isolation, which can be useful if
* the hardware is still running and register values could change.
*
* In order to ensure stale data isn't used from the cache, call
* HardwareRegister::flushAll() before beginning isolation on a new attention.
* Also, HardwareRegister::flushAll() should be called when the isolator is
* uninitialized before the rest of the isolation objects are deleted.
*/
class HardwareRegister : public Register
{
public: // Aliases
using Ptr = std::shared_ptr<HardwareRegister>;
using ConstPtr = std::shared_ptr<const HardwareRegister>;
using Key = std::pair<RegisterId_t, Instance_t>;
public:
/** @brief Pure virtual destructor. */
virtual ~HardwareRegister() = 0;
protected:
/**
* @brief Constructor from components.
* @param i_id Unique ID for this register.
* @param i_instance Instance of this register
* @param i_flags Attribute flags for this register.
*/
HardwareRegister(RegisterId_t i_id, Instance_t i_instance,
RegisterAttributeFlags_t i_flags) :
Register(),
iv_id(i_id), iv_instance(i_instance), iv_flags(i_flags)
{}
private: // Instance variables
/** The unique ID for this register. */
const RegisterId_t iv_id;
/** A register may have multiple instances. All of which will have the same
* ID. This variable is used to distinguish between each instance of the
* register. */
const Instance_t iv_instance;
/** The hardware access level of this register (read/write, read-only,
* write-only, etc.). */
const RegisterAttributeFlags_t iv_flags;
public: // Accessor functions
/* @return The unique ID for this register. */
RegisterId_t getId() const
{
return iv_id;
}
/* @return The instance of this register. */
Instance_t getInstance() const
{
return iv_instance;
}
/** @return The register/instance key. */
Key getKey() const
{
return {iv_id, iv_instance};
}
/** @return True if given flag is enabled, false if disabled. */
bool queryAttrFlag(RegisterAttributeFlags_t i_flag) const
{
return (0 != (iv_flags & i_flag));
}
// NOTE: The following are determined by child classes.
/** @return This register's type. */
virtual RegisterType_t getType() const = 0;
/** @return The address of this register. */
virtual RegisterAddress_t getAddress() const = 0;
/** @return The size (in bytes) of this register. */
virtual size_t getSize() const = 0;
public: // Operators
/** @brief Equals operator. */
bool operator==(const HardwareRegister& i_r) const
{
// In general, comparing the ID and instance should be enough. However,
// no error will be thrown when adding the register to the flyweights
// and any other field differs. Therefore, all fields will be used and
// invalid duplicates will be found when adding the register pointers
// to the IsolationChip objects.
return (getAddress() == i_r.getAddress()) && (getId() == i_r.getId()) &&
(getInstance() == i_r.getInstance()) &&
(getType() == i_r.getType()) && (iv_flags == i_r.iv_flags);
}
/** @brief Less than operator. */
bool operator<(const HardwareRegister& i_r) const
{
// In general, comparing the ID and instance should be enough. However,
// no error will be thrown when adding the register to the flyweights
// and any other field differs. Therefore, all fields will be used and
// invalid duplicates will be found when adding the register pointers
// to the IsolationChip objects.
if (getAddress() < i_r.getAddress())
{
return true;
}
else if (getAddress() == i_r.getAddress())
{
if (getId() < i_r.getId())
{
return true;
}
else if (getId() == i_r.getId())
{
if (getInstance() < i_r.getInstance())
{
return true;
}
else if (getInstance() == i_r.getInstance())
{
if (getType() < i_r.getType())
{
return true;
}
else if (getType() == i_r.getType())
{
return (iv_flags < i_r.iv_flags);
}
}
}
}
return false;
}
public:
/** Function overloaded from parent Register class. */
const BitString* getBitString(const Chip& i_chip) const;
/**
* @brief Reads a register from hardware via the user interface APIs.
* @param i_chip The target chip in which this register belongs.
* @param i_force When false, this function will only read from hardware if
* an entry for this instance does not already exist in the
* register cache. When true, the entry in the register
* cache is flushed, if it exists. Then this function will
* read from hardware and update the cache.
* @return See the return code from the registerRead() user interface API.
*/
bool read(const Chip& i_chip, bool i_force = false) const;
#ifdef __HEI_ENABLE_HW_WRITE
/**
* @brief Writes the value stored in the register cache to hardware via the
* user interface APIs.
* @param i_chip The target chip in which this register belongs.
* @return See the return code from the registerWrite() user interface API.
*/
bool write(const Chip& i_chip) const;
#endif // __HEI_ENABLE_HW_WRITE
protected:
/**
* @brief Provides access to this register's BitString.
*
* WARNING: Allowing public access to this function may be dangerous. For
* now it should be left as protected.
*
* @param i_chip The target chip in which this register belongs.
* @return A reference to the BitString.
*/
BitString& accessBitString(const Chip& i_chip);
private: // Register cache class variable
/**
* @brief Caches the contents of registers read from hardware.
*
* The goal is to create a snapshot of the hardware register contents as
* close to the reported attention as possible. This snapshot is then used
* for additional analysis/debug when needed.
*/
class Cache
{
public:
/** @brief Default constructor. */
Cache() = default;
/** @brief Destructor. */
~Cache() = default;
/** @brief Copy constructor. */
Cache(const Cache&) = delete;
/** @brief Assignment operator. */
Cache& operator=(const Cache&) = delete;
/**
* @brief Queries if a specific entry exists in the cache.
* @param i_chip The target chip.
* @param i_hwReg The target register.
* @return True if the entry exists, false otherwise.
*/
bool query(const Chip& i_chip, const HardwareRegister* i_hwReg) const;
/**
* @brief Returns the data buffer for the given chip and register.
* @param i_chip The target chip.
* @param i_hwReg The target register.
* @return A reference to the BitString containing the register data.
* @note If an entry does not exist in the cache, an entry will be
* created and the BitString will be initialized to 0.
*/
BitString& access(const Chip& i_chip, const HardwareRegister* i_hwReg);
/** @brief Flushes entire contents from cache. */
void flush();
/**
* @brief Removes a single register from the cache.
* @param i_chip The target chip.
* @param i_hwReg The target register.
*/
void flush(const Chip& i_chip, const HardwareRegister* i_hwReg);
private:
/**
* @brief Stores a BitStringBuffer for each HardwareRegister per Chip.
*
* The HardwareRegister keys will just be pointers to the isolation
* objects created in the main initialize() API. Those should exist
* until the main uninitialize() API is called. It is important that the
* cache is flushed at the beginning of the uninitialize() API before
* the rest of the isolation objects are deleted.
*
* The Chip keys are copies of the objects passed to the isolator
* because the user application is responsible for storage of the
* objects passed to the isolator. We don't want to chance a Chip was
* created as a local variable that goes out of scope, or other similar
* situations.
*/
std::map<Chip, std::map<const HardwareRegister*, BitString*>> iv_cache;
};
/** This allows all HardwareRegister objects access to the cache. */
static Cache cv_cache;
public: // Register cache management functions.
/** @brief Flushes the entire register cache. */
static void flushAll()
{
cv_cache.flush();
}
/**
* @brief Flushes this register from the cache.
* @param i_chip The target chip in which this register belongs.
*/
void flush(const Chip& i_chip) const
{
cv_cache.flush(i_chip, this);
}
private: // Register cache management functions.
/**
* @param i_chip The target chip in which this register belongs.
* @return True if an entry for this register exist in this cache.
*/
bool queryCache(const Chip& i_chip) const
{
return cv_cache.query(i_chip, this);
}
/**
* @param i_chip The target chip in which this register belongs.
* @return A reference to this register's BitString in cache.
*/
BitString& accessCache(const Chip& i_chip) const
{
return cv_cache.access(i_chip, this);
}
};
} // end namespace libhei