blob: 0525188811523ea2ca87a25e94771cd0130ab29b [file] [log] [blame]
/*
* SPDX-License-Identifier: BSD-3-Clause
* SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
*/
#ifndef DEV_GRANULE_H
#define DEV_GRANULE_H
#include <assert.h>
#include <atomics.h>
#include <dev_type.h>
#include <errno.h>
#include <granule_lock.h>
#include <memory.h>
#include <stdbool.h>
#include <utils_def.h>
/* Maximum value defined by the 'refcount' field width in dev_granule descriptor */
#define DEV_REFCOUNT_MAX (unsigned char) \
((U(1) << DEV_GRN_REFCOUNT_WIDTH) - U(1))
/* Dev Granule descriptor fields access macros */
#define DEV_LOCKED(g) \
((SCA_READ8(&(g)->descriptor) & DEV_GRN_LOCK_BIT) != 0U)
#define DEV_REFCOUNT(g) \
(SCA_READ8(&(g)->descriptor) & DEV_REFCOUNT_MASK)
#define DEV_STATE(g) \
(unsigned char)EXTRACT(DEV_GRN_STATE, SCA_READ8(&(g)->descriptor))
/*
* Return refcount value using atomic read.
*/
static inline unsigned char dev_granule_refcount_read(struct dev_granule *g)
{
assert(g != NULL);
return DEV_REFCOUNT(g);
}
/*
* Return refcount value using atomic read with acquire semantics.
*
* Must be called with dev_granule lock held.
*/
static inline unsigned char dev_granule_refcount_read_acquire(struct dev_granule *g)
{
assert((g != NULL) && DEV_LOCKED(g));
return SCA_READ8_ACQUIRE(&g->descriptor) & DEV_REFCOUNT_MASK;
}
/*
* Sanity-check unlocked dev_granule invariants.
* This check is performed just after acquiring the lock and/or just before
* releasing the lock.
*
* These invariants must hold for any dev_granule which is unlocked.
*
* These invariants may not hold transiently while a dev_granule is locked (e.g.
* when transitioning to/from delegated state).
*
* Note: this function is purely for debug/documentation purposes, and is not
* intended as a mechanism to ensure correctness.
*/
static inline void __dev_granule_assert_unlocked_invariants(struct dev_granule *g,
unsigned char state)
{
(void)g;
switch (state) {
case DEV_GRANULE_STATE_NS:
assert(DEV_REFCOUNT(g) == 0U);
break;
case DEV_GRANULE_STATE_DELEGATED:
assert(DEV_REFCOUNT(g) == 0U);
break;
case DEV_GRANULE_STATE_MAPPED:
assert(DEV_REFCOUNT(g) == 0U);
break;
default:
/* Unknown dev_granule type */
assert(false);
}
}
/*
* Return the state of unlocked dev_granule.
* This function should be used only for NS dev_granules where RMM performs NS
* specific operations on the granule.
*/
static inline unsigned char dev_granule_unlocked_state(struct granule *g)
{
assert(g != NULL);
/* NOLINTNEXTLINE(clang-analyzer-core.NullDereference) */
return DEV_STATE(g);
}
/* Must be called with dev_granule lock held */
static inline unsigned char dev_granule_get_state(struct dev_granule *g)
{
assert((g != NULL) && DEV_LOCKED(g));
/* NOLINTNEXTLINE(clang-analyzer-core.NullDereference) */
return DEV_STATE(g);
}
/* Must be called with dev_granule lock held */
static inline void dev_granule_set_state(struct dev_granule *g, unsigned char state)
{
unsigned char val;
assert((g != NULL) && DEV_LOCKED(g));
/* NOLINTNEXTLINE(clang-analyzer-core.NullDereference) */
val = g->descriptor & DEV_STATE_MASK;
/* cppcheck-suppress misra-c2012-10.3 */
val ^= state << DEV_GRN_STATE_SHIFT;
/*
* Atomically EOR val while keeping the bits for refcount and
* bitlock as 0 which would preserve their values in memory.
*/
(void)atomic_eor_8(&g->descriptor, val);
}
/*
* Acquire the bitlock and then check expected state
* Fails if unexpected locking sequence detected.
* Also asserts if invariant conditions are met.
*/
static inline bool dev_granule_lock_on_state_match(struct dev_granule *g,
unsigned char expected_state)
{
dev_granule_bitlock_acquire(g);
if (dev_granule_get_state(g) != expected_state) {
dev_granule_bitlock_release(g);
return false;
}
__dev_granule_assert_unlocked_invariants(g, expected_state);
return true;
}
/*
* Used when we're certain of the type of an object (e.g. because we hold a
* reference to it). In these cases we should never fail to acquire the lock.
*/
static inline void dev_granule_lock(struct dev_granule *g,
unsigned char expected_state)
{
__unused bool locked = dev_granule_lock_on_state_match(g, expected_state);
assert(locked);
}
static inline void dev_granule_unlock(struct dev_granule *g)
{
__dev_granule_assert_unlocked_invariants(g, dev_granule_get_state(g));
dev_granule_bitlock_release(g);
}
/* Transtion state to @new_state and unlock the dev_granule */
static inline void dev_granule_unlock_transition(struct dev_granule *g,
unsigned char new_state)
{
dev_granule_set_state(g, new_state);
dev_granule_unlock(g);
}
/*
* Takes a valid pointer to a struct dev_granule, corresponding to dev_type
* and returns the dev_granule physical address.
*
* This is purely a lookup, and provides no guarantees about the attributes of
* the dev_granule (i.e. whether it is locked, its state or its reference count).
*/
unsigned long dev_granule_addr(const struct dev_granule *g, enum dev_type type);
/*
* Takes an aligned dev_granule address, returns a pointer to the corresponding
* struct dev_granule and sets device granule type in address passed in @type.
*
* This is purely a lookup, and provides no guarantees about the attributes of
* the granule (i.e. whether it is locked, its state or its reference count).
*/
struct dev_granule *addr_to_dev_granule(unsigned long addr, enum dev_type *type);
/*
* Verifies whether @addr is a valid dev_granule physical address, returns
* a pointer to the corresponding struct dev_granule and sets device granule type.
*
* This is purely a lookup, and provides no guarantees w.r.t the state of the
* granule (e.g. locking).
*
* Returns:
* Pointer to the struct dev_granule if @addr is a valid dev_granule physical
* address and device granule type in address passed in @type.
* NULL if any of:
* - @addr is not aligned to the size of a granule.
* - @addr is out of range.
*/
struct dev_granule *find_dev_granule(unsigned long addr, enum dev_type *type);
/*
* Obtain a pointer to a locked dev_granule at @addr if @addr is a valid dev_granule
* physical address and the state of the dev_granule at @addr is @expected_state and
* set device granule type.
*
* Returns:
* A valid dev_granule pointer if @addr is a valid dev_granule physical address
* and device granule type in address passed in @type.
* NULL if any of:
* - @addr is not aligned to the size of a granule.
* - @addr is out of range.
* - if the state of the dev_granule at @addr is not @expected_state.
*/
struct dev_granule *find_lock_dev_granule(unsigned long addr,
unsigned char expected_state,
enum dev_type *type);
/*
* Refcount field occupies LSB bits of the dev_granule descriptor,
* and functions which modify its value can operate directly on
* the whole 8-bit word without masking, provided that the result
* doesn't exceed DEV_REFCOUNT_MAX or set to negative number.
*/
/*
* Atomically increments the reference counter of the granule by @val.
*
* Must be called with granule lock held.
*/
static inline void dev_granule_refcount_inc(struct dev_granule *g,
unsigned char val)
{
uint8_t old_refcount __unused;
assert((g != NULL) && DEV_LOCKED(g));
old_refcount = atomic_load_add_8(&g->descriptor, val) &
DEV_REFCOUNT_MASK;
assert((old_refcount + val) <= DEV_REFCOUNT_MAX);
}
/*
* Atomically increments the reference counter of the granule.
*
* Must be called with granule lock held.
*/
static inline void atomic_dev_granule_get(struct dev_granule *g)
{
dev_granule_refcount_inc(g, 1U);
}
/*
* Atomically increments the reference counter of the dev_granule by @val.
*
* Must be called with dev_granule lock held.
*/
static inline void dev_granule_refcount_dec(struct dev_granule *g, unsigned char val)
{
uint8_t old_refcount __unused;
assert((g != NULL) && DEV_LOCKED(g));
/* coverity[misra_c_2012_rule_10_1_violation:SUPPRESS] */
old_refcount = atomic_load_add_8(&g->descriptor, (uint8_t)(-val)) &
DEV_REFCOUNT_MASK;
assert(old_refcount >= val);
}
/*
* Atomically decrements the reference counter of the dev_granule.
*
* Must be called with dev_granule lock held.
*/
static inline void atomic_dev_granule_put(struct dev_granule *g)
{
dev_granule_refcount_dec(g, 1U);
}
/*
* Atomically decrements the reference counter of the dev_granule.
* Stores to memory with release semantics.
*/
static inline void atomic_dev_granule_put_release(struct dev_granule *g)
{
uint8_t old_refcount __unused;
assert(g != NULL);
old_refcount = atomic_load_add_release_8(&g->descriptor,
(uint8_t)(-1)) & DEV_REFCOUNT_MASK;
assert(old_refcount != 0U);
}
/*
* Returns 'true' if dev_granule is locked, 'false' otherwise
*
* This function is only meant to be used for verification and testing,
* and this functionlaity is not required for RMM operations.
*/
static inline bool is_dev_granule_locked(struct dev_granule *g)
{
assert(g != NULL);
return DEV_LOCKED(g);
}
#endif /* DEV_GRANULE_H */