blob: c4a1682e09b17baec92cf7dd7425954e59e1508f [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_MEMORY_H_
#define OPENTITAN_SW_DEVICE_LIB_BASE_MEMORY_H_
/**
* @file
* @brief OpenTitan Device Memory Library
*
* This library provides memory functions for aligned word accesses, and some
* useful functions from the C library's <string.h>.
*/
#include <stdalign.h>
#include <stddef.h>
#include <stdint.h>
// NB: --mabi=cheriot-baremetal generates direct C calls & unmangled symbols
#ifdef _CHERIOT_BAREMETAL_
#undef __cheri_libcall
#define __cheri_libcall
#endif
#ifndef OT_PLATFORM_RV32
#include <string.h>
#endif
#include "sw/device/lib/base/macros.h"
// When compiling unit tests on the host machine, we must mangle the names of
// OpenTitan's memory functions to disambiguate them from libc's variants.
// Otherwise, the compiler could error when it encounters conflicting
// declarations. For instance, OpenTitan's `memcpy()` could have a different
// exception specifier than the one declared by the system header <string.h>.
// (The <string.h> include is unavoidable because it's pulled in by GoogleTest.)
//
// Consumers of this header should almost always call the unmangled functions.
// Device builds will naturally use OpenTitan's definitions and host builds will
// use the system's definitions. However, nothing is stopping host builds from
// calling the mangled function names; see :memory_unittest for an example.
//
// Separately, ASan segfaults during initialization when it calls our
// user-defined memory functions. Prefixing the troublesome functions works
// around this ASan bug, enabling us to run the unit tests with ASan enabled.
// See https://github.com/lowRISC/opentitan/issues/13826.
#ifdef OT_PLATFORM_RV32
#define OT_PREFIX_IF_NOT_RV32(name) __cheri_libcall name
#else
#define OT_PREFIX_IF_NOT_RV32(name) ot_##name
#endif
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Computes how many bytes `addr` is ahead of the previous 32-bit word alignment
* boundary.
*/
inline ptrdiff_t misalignment32_of(uintptr_t addr) {
return addr % alignof(uint32_t);
}
/**
* Load a word from memory directly, bypassing aliasing rules.
*
* ISO C forbids, in general, casting a pointer to non-character types and
* reading them, though it is frequently necessary to read exactly one word out
* of a `void *`. This function performs that action in a manner which is
* well-defined.
*
* Of course, `ptr` must point to word-aligned memory that is at least one word
* wide. To do otherwise is Undefined Behavior. It goes without saying that the
* memory this function intents to read must be initialized.
*
* This function has reordering properties as weak as a normal, non-atomic,
* non-volatile load.
*
* @param ptr a word-aligned pointer pointed to at least four bytes of memory.
* @return the word `ptr` points to.
*/
inline uint32_t read_32(const void *ptr) {
// Both GCC and Clang optimize the code below into a single word-load on most
// platforms. It is necessary and sufficient to indicate to the compiler that
// the pointer points to four bytes of four-byte-aligned memory.
//
// Failing to get that particular codegen in either GCC or Clang with -O2 or
// -Os set shall be considered a bug in this function. The same applies to
// `write32()`.
ptr = __builtin_assume_aligned(ptr, alignof(uint32_t));
uint32_t val;
__builtin_memcpy(&val, ptr, sizeof(uint32_t));
return val;
}
/**
* Load a 64-bit word from memory directly, bypassing aliasing rules.
*
* ISO C forbids, in general, casting a pointer to non-character types and
* reading them, though it is frequently necessary to read exactly one 64-bit
* word out of a `void *`. This function performs that action in a manner which
* is well-defined.
*
* Of course, `ptr` must point to word-aligned memory that is at least one
* 64-bit word wide. To do otherwise is Undefined Behavior. It goes without
* saying that the memory this function intents to read must be initialized.
*
* This function has reordering properties as weak as a normal, non-atomic,
* non-volatile load.
*
* @param ptr a word-aligned pointer pointed to at least four bytes of memory.
* @return the word `ptr` points to.
*/
inline uint64_t read_64(const void *ptr) {
// Both GCC and Clang optimize the code below into a single word-load on most
// platforms. It is necessary and sufficient to indicate to the compiler that
// the pointer points to four bytes of four-byte-aligned memory.
//
// Failing to get that particular codegen in either GCC or Clang with -O2 or
// -Os set shall be considred a bug in this function. The same applies to
// `write32()`.
ptr = __builtin_assume_aligned(ptr, alignof(uint64_t));
uint64_t val;
__builtin_memcpy(&val, ptr, sizeof(uint64_t));
return val;
}
/**
* Store a word to memory directly, bypassing aliasing rules.
*
* ISO C forbids, in general, casting a pointer to non-character types and
* reading them, though it is frequently necessary to write exactly one word to
* a `void *`. This function performs that action in a manner which is
* well-defined.
*
* Of course, `ptr` must point to word-aligned memory that is at least one word
* wide. To do otherwise is Undefined Behavior.
*
* This function has reordering properties as weak as a normal, non-atomic,
* non-volatile load.
*
* @param value the value to store.
* @param ptr a word-aligned pointer pointed to at least four bytes of memory.
*/
inline void write_32(uint32_t value, void *ptr) {
// Both GCC and Clang optimize the code below into a single word-store on most
// platforms. See the comment in `read_32()` for more implementation-private
// information.
ptr = __builtin_assume_aligned(ptr, alignof(uint32_t));
__builtin_memcpy(ptr, &value, sizeof(uint32_t));
}
/**
* Store a 64-bit word to memory directly, bypassing aliasing rules.
*
* ISO C forbids, in general, casting a pointer to non-character types and
* reading them, though it is frequently necessary to write exactly one 64-bit
* word to a `void *`. This function performs that action in a manner which is
* well-defined.
*
* Of course, `ptr` must point to word-aligned memory that is at least one
* 64-bit word wide. To do otherwise is Undefined Behavior.
*
* This function has reordering properties as weak as a normal, non-atomic,
* non-volatile load.
*
* @param value the value to store.
* @param ptr a word-aligned pointer pointed to at least four bytes of memory.
*/
inline void write_64(uint64_t value, void *ptr) {
// Both GCC and Clang optimize the code below into a single word-store on most
// platforms. See the comment in `read_64()` for more implementation-private
// information.
ptr = __builtin_assume_aligned(ptr, alignof(uint64_t));
__builtin_memcpy(ptr, &value, sizeof(uint64_t));
}
/**
* Copy memory between non-overlapping regions.
*
* This function conforms to the semantics defined in ISO C11 S7.23.2.1.
*
* This function will be provided by the platform's libc implementation for host
* builds.
*
* @param dest the region to copy to.
* @param src the region to copy from.
* @param len the number of bytes to copy.
* @return the value of `dest`.
*/
void *OT_PREFIX_IF_NOT_RV32(memcpy)(void *OT_RESTRICT dest,
const void *OT_RESTRICT src, size_t len);
/**
* Set a region of memory to a particular byte value.
*
* This function conforms to the semantics defined in ISO C11 S7.23.6.1.
*
* This function will be provided by the platform's libc implementation for host
* builds.
*
* @param dest the region to write to.
* @param value the value, converted to a byte, to write to each byte cell.
* @param len the number of bytes to write.
* @return the value of `dest`.
*/
void *OT_PREFIX_IF_NOT_RV32(memset)(void *dest, int value, size_t len);
/**
* Compare two (potentially overlapping) regions of memory for byte-wise
* lexicographic order.
*
* This function conforms to the semantics defined in ISO C11 S7.24.4.1.
*
* This function will be provided by the platform's libc implementation for host
* builds.
*
* @param lhs the left-hand-side of the comparison.
* @param rhs the right-hand-side of the comparison.
* @param len the length of both regions, in bytes.
* @return a zero, positive, or negative integer, corresponding to the
* contingencies of `lhs == rhs`, `lhs > rhs`, and `lhs < rhs` (as buffers, not
* pointers), respectively.
*/
int OT_PREFIX_IF_NOT_RV32(memcmp)(const void *lhs, const void *rhs, size_t len);
/**
* Compare two (potentially overlapping) regions of memory for reverse
* byte-wise lexicographic order (i.e. the same as memcmp, but starting from
* the end of the memory regions).
*
* Can be used for arithmetic comparison of little-endian buffers.
*
* This function is provided by OpenTitan in host builds, not by the platform's
* libc implementation.
*
* @param lhs the left-hand-side of the comparison.
* @param rhs the right-hand-side of the comparison.
* @param len the length of both regions, in bytes.
* @return a zero, positive, or negative integer, corresponding to the
* contingencies of `lhs == rhs`, `lhs > rhs`, and `lhs < rhs` (as buffers, not
* pointers), respectively.
*/
int memrcmp(const void *lhs, const void *rhs, size_t len);
/**
* Search a region of memory for the first occurrence of a particular byte
* value.
*
* This function conforms to the semantics defined in ISO C11 S7.24.5.1.
*
* Since libbase does not provide a `strlen()` function, this function can be
* used as an approximation: `memchr(my_str, 0, SIZE_MAX) - my_str`.
*
* This function will be provided by the platform's libc implementation for host
* builds.
*
* @param ptr the region to search.
* @param value the value, converted to a byte, to search for.
* @param len the length of the region, in bytes.
* @return a pointer to the found value, or NULL.
*/
void *OT_PREFIX_IF_NOT_RV32(memchr)(const void *ptr, int value, size_t len);
/**
* Search a region of memory for the last occurrence of a particular byte value.
*
* This function is not specified by C11, but is named for a well-known glibc
* extension.
*
* @param ptr the region to search.
* @param value the value, converted to a byte, to search for.
* @param len the length of the region, in bytes.
* @return a pointer to the found value, or NULL.
*/
void *OT_PREFIX_IF_NOT_RV32(memrchr)(const void *ptr, int value, size_t len);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#undef OT_PREFIX_IF_NOT_RV32
#endif // OPENTITAN_SW_DEVICE_LIB_BASE_MEMORY_H_