blob: cec2dced43cfd02a0c9e624ae25b2be42dd31327 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifndef OPENTITAN_SW_DEVICE_LIB_BASE_MMIO_H_
#define OPENTITAN_SW_DEVICE_LIB_BASE_MMIO_H_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "sw/device/lib/base/bitfield.h"
#include "sw/device/lib/base/macros.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* @file
* @brief Memory-mapped IO functions, for volatile access.
*
* Memory-mapped IO functions, which either map to volatile accesses, or can be
* replaced with instrumentation calls at compile time, for use with tests.
*
* Compiling translation units that pull in this header with `-DMOCK_MMIO` will
* disable the definitions of `mmio_region_read` and `mmio_region_write`. These
* symbols can then be defined by a test harness to allow for instrumentation of
* MMIO accesses.
*/
/**
* 2020-06: We're transitioning to a more efficient manner of using our MMIO
* APIs, where the DIFs explicitly read, then modify, then write. All the
* `*_nonatomic_*` functions in this DIF are deprecated and will be removed
* eventually, leaving only the `read<N>`, `write<N>`, and `memcpy` functions.
*
* For the moment, we are not adding `__attribute__((deprecated(reason)))` using
* this macro, because most code still uses the old version, but at some point
* we will add that expansion. This should be seen as a note to humans, not
* computers (yet).
*/
#define MMIO_DEPRECATED
#ifdef OT_PLATFORM_RV32
/**
* An mmio_region_t is an opaque handle to an MMIO region; it should only be
* modified using the functions provided in this header.
*/
typedef struct mmio_region {
volatile void *base;
} mmio_region_t;
/**
* Create a new `mmio_region_t` from the given address.
*
* @param address an address to an MMIO region.
* @return a `mmio_region_t` value representing that region.
*/
OT_WARN_UNUSED_RESULT
inline mmio_region_t mmio_region_from_addr(uintptr_t address) {
return (mmio_region_t){
.base = (volatile void *)address,
};
}
/**
* Reads an aligned uint8_t from the MMIO region `base` at the given byte
* offset.
*
* This function is guaranteed to commit a read to memory, and will not be
* reordered with respect to other MMIO manipulations.
*
* @param base the region to read from.
* @param offset the offset to read at, in bytes.
* @return the read value.
*/
OT_WARN_UNUSED_RESULT
inline uint8_t mmio_region_read8(mmio_region_t base, ptrdiff_t offset) {
return ((volatile uint8_t *)base.base)[offset / sizeof(uint8_t)];
}
/**
* Reads an aligned uint32_t from the MMIO region `base` at the given byte
* offset.
*
* This function is guaranteed to commit a read to memory, and will not be
* reordered with respect to other MMIO manipulations.
*
* @param base the region to read from.
* @param offset the offset to read at, in bytes.
* @return the read value.
*/
OT_WARN_UNUSED_RESULT
inline uint32_t mmio_region_read32(mmio_region_t base, ptrdiff_t offset) {
return ((volatile uint32_t *)base.base)[offset / sizeof(uint32_t)];
}
/**
* Writes an aligned uint8_t to the MMIO region `base` at the given byte
* offset.
*
* This function is guaranteed to commit a write to memory, and will not be
* reordered with respect to other region manipulations.
*
* @param base the region to write to.
* @param offset the offset to write at, in bytes.
* @param value the value to write.
*/
inline void mmio_region_write8(mmio_region_t base, ptrdiff_t offset,
uint8_t value) {
((volatile uint8_t *)base.base)[offset / sizeof(uint8_t)] = value;
}
/**
* Writes an aligned uint8_t to the MMIO region `base` at the given byte
* offset via two subsequent write operations.
*
* This function is guaranteed to commit a write to memory, and will not be
* reordered with respect to other region manipulations.
*
* @param base the region to write to.
* @param offset the offset to write at, in bytes.
* @param value the value to write.
*/
inline void mmio_region_write8_shadowed(mmio_region_t base, ptrdiff_t offset,
uint8_t value) {
((volatile uint8_t *)base.base)[offset / sizeof(uint8_t)] = value;
((volatile uint8_t *)base.base)[offset / sizeof(uint8_t)] = value;
}
/**
* Writes an aligned uint32_t to the MMIO region `base` at the given byte
* offset.
*
* This function is guaranteed to commit a write to memory, and will not be
* reordered with respect to other region manipulations.
*
* @param base the region to write to.
* @param offset the offset to write at, in bytes.
* @param value the value to write.
*/
inline void mmio_region_write32(mmio_region_t base, ptrdiff_t offset,
uint32_t value) {
((volatile uint32_t *)base.base)[offset / sizeof(uint32_t)] = value;
}
/**
* Writes an aligned uint32_t to the MMIO region `base` at the given byte
* offset via two subsequent write operations.
*
* This function is guaranteed to commit a write to memory, and will not be
* reordered with respect to other region manipulations.
*
* @param base the region to write to.
* @param offset the offset to write at, in bytes.
* @param value the value to write.
*/
inline void mmio_region_write32_shadowed(mmio_region_t base, ptrdiff_t offset,
uint32_t value) {
((volatile uint32_t *)base.base)[offset / sizeof(uint32_t)] = value;
((volatile uint32_t *)base.base)[offset / sizeof(uint32_t)] = value;
}
#else // OT_PLATFORM_RV32
/**
* "Instrumented" mmio_region_t.
*
* Instead of containing a volatile pointer, mmio_region_t becomes a `void *`
* when `-DMOCK_MMIO` is enabled. This makes it incompatible with the non-mock
* version of `mmio_region_t`, which prevents users from being able to access
* the pointer inside.
*/
typedef struct mmio_region {
void *mock;
} mmio_region_t;
/**
* Stubbed-out read/write operations for overriding by a testing library.
*/
OT_WARN_UNUSED_RESULT
mmio_region_t mmio_region_from_addr(uintptr_t address);
OT_WARN_UNUSED_RESULT
uint8_t mmio_region_read8(mmio_region_t base, ptrdiff_t offset);
OT_WARN_UNUSED_RESULT
uint32_t mmio_region_read32(mmio_region_t base, ptrdiff_t offset);
void mmio_region_write8(mmio_region_t base, ptrdiff_t offset, uint8_t value);
void mmio_region_write32(mmio_region_t base, ptrdiff_t offset, uint32_t value);
void mmio_region_write8_shadowed(mmio_region_t base, ptrdiff_t offset,
uint8_t value);
void mmio_region_write32_shadowed(mmio_region_t base, ptrdiff_t offset,
uint32_t value);
#endif // OT_PLATFORM_RV32
/**
* Reads the bits in `mask` from the MMIO region `base` at the given offset.
*
* This function has the same guarantees as `mmio_region_read32()` and
* `mmio_region_write32()`.
*
* @param base the region to mask.
* @param offset the offset to apply the mask at, in bytes.
* @param mask the mask to read from the selected register.
* @param mask_index mask position within the selected register.
* @return return the value of the read mask.
*/
OT_WARN_UNUSED_RESULT
MMIO_DEPRECATED
inline uint32_t mmio_region_read_mask32(mmio_region_t base, ptrdiff_t offset,
uint32_t mask, uint32_t mask_index) {
return bitfield_field32_read(
mmio_region_read32(base, offset),
(bitfield_field32_t){.mask = mask, .index = mask_index});
}
/**
* Checks whether the `bit_index`th bit is set in the MMIO region `base` at
* the given offset.
*
* This function has the same guarantees as `mmio_region_read32()` and
* `mmio_region_write32()`.
*
* @param base the region to mask.
* @param offset the offset to apply the mask at.
* @param bit_index the bit to check.
* @return true if the bit is set, false otherwise
*/
OT_WARN_UNUSED_RESULT
MMIO_DEPRECATED
inline bool mmio_region_get_bit32(mmio_region_t base, ptrdiff_t offset,
uint32_t bit_index) {
return bitfield_bit32_read(mmio_region_read32(base, offset), bit_index);
}
/**
* Clears the bits in `mask` from the MMIO region `base` at the given offset.
*
* This function performs a non-atomic read-write-modify operation on a
* MMIO region.
*
* @param base the region to mask.
* @param offset the offset to apply the mask at, in bytes.
* @param mask the mask to clear from the selected register.
* @param mask_index mask position within the selected register.
*/
MMIO_DEPRECATED
inline void mmio_region_nonatomic_clear_mask32(mmio_region_t base,
ptrdiff_t offset, uint32_t mask,
uint32_t mask_index) {
uint32_t register_value = mmio_region_read32(base, offset);
register_value = bitfield_field32_write(
register_value, (bitfield_field32_t){.mask = mask, .index = mask_index},
0x0);
mmio_region_write32(base, offset, register_value);
}
/**
* Sets the bits in `mask` from the MMIO region `base` at the given offset.
*
* This function performs a non-atomic read-write-modify operation on a
* MMIO region.
*
* @param base the region to mask.
* @param offset the offset to apply the mask at, in bytes.
* @param mask the mask to set on the selected register.
* @param mask_index mask position within the selected register.
*/
MMIO_DEPRECATED
inline void mmio_region_nonatomic_set_mask32(mmio_region_t base,
ptrdiff_t offset, uint32_t mask,
uint32_t mask_index) {
uint32_t register_value = mmio_region_read32(base, offset);
register_value = bitfield_field32_write(
register_value, (bitfield_field32_t){.mask = mask, .index = mask_index},
~0x0u);
mmio_region_write32(base, offset, register_value);
}
/**
* Sets the bits in `mask` from the MMIO region `base` at the given offset.
*
* This function is like `nonatomic_set_mask32`, but does not perform a
* read, for use with write-only memory.
*
* @param base the region to mask.
* @param offset the offset to apply the mask at, in bytes.
* @param mask the mask to set on the selected register.
* @param mask_index mask position within the selected register.
*/
MMIO_DEPRECATED
inline void mmio_region_write_only_set_mask32(mmio_region_t base,
ptrdiff_t offset, uint32_t mask,
uint32_t mask_index) {
uint32_t register_value = 0x0u;
register_value = bitfield_field32_write(
register_value, (bitfield_field32_t){.mask = mask, .index = mask_index},
~0x0u);
mmio_region_write32(base, offset, register_value);
}
/**
* Sets the `field` from the MMIO region `base` at the given `offset`.
*
* This function performs a non-atomic read-write-modify operation on a
* MMIO region. The information of which portion of the register to set, is
* stored in the `field`. The semantics of this operation are similar to the
* `mmio_region_nonatomic_set_mask32`, however the appropriate portion of the
* register is zeroed before it is written to.
*
* @param base the region to set the field in.
* @param offset the offset to set the field at, in bytes.
* @param field field within selected register field to be set.
* @param value value to set the field to.
*/
MMIO_DEPRECATED
inline void mmio_region_nonatomic_set_field32(mmio_region_t base,
ptrdiff_t offset,
bitfield_field32_t field,
uint32_t value) {
uint32_t register_value = mmio_region_read32(base, offset);
register_value = bitfield_field32_write(register_value, field, value);
mmio_region_write32(base, offset, register_value);
}
/**
* Sets the `field` from the MMIO region `base` at the given `offset`.
*
* This function is like `nonatomic_set_field32`, but does not perform a
* read, for use with write-only memory.
*
* @param base the region to set the field in.
* @param offset the offset to set the field at, in bytes.
* @param field field within selected register field to be set.
* @param value value to set field to.
*/
MMIO_DEPRECATED
inline void mmio_region_write_only_set_field32(mmio_region_t base,
ptrdiff_t offset,
bitfield_field32_t field,
uint32_t value) {
uint32_t register_value = 0x0u;
register_value = bitfield_field32_write(register_value, field, value);
mmio_region_write32(base, offset, register_value);
}
/**
* Clears the `bit_index`th bit in the MMIO region `base` at the given offset.
*
* This function has the same guarantees as
* `mmio_region_nonatomic_clear_mask()`.
*
* @param base the region to mask.
* @param offset the offset to apply the mask at.
* @param bit_index the bit to clear.
*/
MMIO_DEPRECATED
inline void mmio_region_nonatomic_clear_bit32(mmio_region_t base,
ptrdiff_t offset,
uint32_t bit_index) {
uint32_t register_value = mmio_region_read32(base, offset);
register_value = bitfield_bit32_write(register_value, bit_index, false);
mmio_region_write32(base, offset, register_value);
}
/**
* Sets the `bit_index`th bit in the MMIO region `base` at the given offset.
*
* This function has the same guarantees as `mmio_region_nonatomic_set_mask()`.
*
* @param base the region to mask.
* @param offset the offset to apply the mask at.
* @param bit_index the bit to set.
*/
MMIO_DEPRECATED
inline void mmio_region_nonatomic_set_bit32(mmio_region_t base,
ptrdiff_t offset,
uint32_t bit_index) {
uint32_t register_value = mmio_region_read32(base, offset);
register_value = bitfield_bit32_write(register_value, bit_index, true);
mmio_region_write32(base, offset, register_value);
}
/**
* Sets the `bit_index`th bit in the MMIO region `base` at the given offset.
*
* This function is like `nonatomic_set_bit32`, but does not perform a read, for
* use with write-only memory.
*
* There is no `write_only_clear32`, since such a function would be a no-op.
*
* @param base the region to mask.
* @param offset the offset to apply the mask at.
* @param bit_index the bit to set.
*/
MMIO_DEPRECATED
inline void mmio_region_write_only_set_bit32(mmio_region_t base,
ptrdiff_t offset,
uint32_t bit_index) {
uint32_t register_value = 0x0u;
register_value = bitfield_bit32_write(register_value, bit_index, true);
mmio_region_write32(base, offset, register_value);
}
/**
* Copies a block of memory from MMIO to main memory while ensuring that MMIO
* accesses are both word-sized and word-aligned.
*
* This function may perform up to `len/4 + 2` volatile reads to handle
* unaligned accesses.
*
* @param base the MMIO region to read from.
* @param offset the offset to start reading from, in bytes.
* @param dest the main memory location to start writing to.
* @param len number of bytes to copy.
*/
void mmio_region_memcpy_from_mmio32(mmio_region_t base, uint32_t offset,
void *dest, size_t len);
/**
* Copies a block of memory from main memory to MMIO while ensuring that MMIO
* accesses are both word-sized and word-aligned.
*
* Unaligned MMIO blocks are handled by performing a read-modify-write for the
* boundary words.
*
* @param base the MMIO region to write to.
* @param offset the offset to start writing to, in bytes.
* @param src the main memory location to start reading from.
* @param len number of bytes to copy.
*/
void mmio_region_memcpy_to_mmio32(mmio_region_t base, uint32_t offset,
const void *src, size_t len);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // OPENTITAN_SW_DEVICE_LIB_BASE_MMIO_H_