include: riscv: add RISC-V arch atomic instructions

Add RISC-V architecture specific atomic instructions, not present in
atomic_builtin.h

Signed-off-by: Aleksandar Cecaric <aleksandar.cecaric@nextsilicon.com>
This commit is contained in:
Aleksandar Cecaric 2024-05-25 13:49:13 +02:00 committed by Henrik Brix Andersen
commit 0f1d93cb4f
5 changed files with 236 additions and 0 deletions

View file

@ -0,0 +1,149 @@
/**
* Copyright (c) 2024 NextSilicon
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_ARCH_RISCV_ATOMIC_H_
#define ZEPHYR_INCLUDE_ARCH_RISCV_ATOMIC_H_
#ifdef __cplusplus
extern "C" {
#endif
/* The standard RISC-V atomic-instruction extension, "A", specifies
* the number of instructions that atomically read-modify-write memory,
* which RISC-V harts should support in order to synchronise harts
* running in the same memory space. This is the subset of RISC-V
* atomic-instructions not present in atomic_builtin.h file.
*/
#ifdef CONFIG_64BIT
static ALWAYS_INLINE atomic_val_t atomic_swap(const atomic_t *target, atomic_val_t newval)
{
atomic_val_t ret;
__asm__ volatile("amoswap.d.aq %0, %1, %2"
: "=r"(ret)
: "r"(newval), "A"(*target)
: "memory");
return ret;
}
static ALWAYS_INLINE atomic_val_t atomic_max(atomic_t *target, atomic_val_t value)
{
atomic_val_t ret;
__asm__ volatile("amomax.d.aq %0, %1, %2"
: "=r"(ret)
: "r"(value), "A"(*target)
: "memory");
return ret;
}
static ALWAYS_INLINE atomic_val_t atomic_min(atomic_t *target, atomic_val_t value)
{
atomic_val_t ret;
__asm__ volatile("amomin.d.aq %0, %1, %2"
: "=r"(ret)
: "r"(value), "A"(*target)
: "memory");
return ret;
}
static ALWAYS_INLINE atomic_val_t atomic_maxu(unsigned long *target, unsigned long value)
{
unsigned long ret;
__asm__ volatile("amomaxu.d.aq %0, %1, %2"
: "=r"(ret)
: "r"(value), "A"(*target)
: "memory");
return ret;
}
static ALWAYS_INLINE atomic_val_t atomic_minu(unsigned long *target, unsigned long value)
{
unsigned long ret;
__asm__ volatile("amominu.d.aq %0, %1, %2"
: "=r"(ret)
: "r"(value), "A"(*target)
: "memory");
return ret;
}
#else
static ALWAYS_INLINE atomic_val_t atomic_swap(const atomic_t *target, atomic_val_t newval)
{
atomic_val_t ret;
__asm__ volatile("amoswap.w.aq %0, %1, %2"
: "=r"(ret)
: "r"(newval), "A"(*target)
: "memory");
return ret;
}
static ALWAYS_INLINE atomic_val_t atomic_max(atomic_t *target, atomic_val_t value)
{
atomic_val_t ret;
__asm__ volatile("amomax.w.aq %0, %1, %2"
: "=r"(ret)
: "r"(value), "A"(*target)
: "memory");
return ret;
}
static ALWAYS_INLINE atomic_val_t atomic_min(atomic_t *target, atomic_val_t value)
{
atomic_val_t ret;
__asm__ volatile("amomin.w.aq %0, %1, %2"
: "=r"(ret)
: "r"(value), "A"(*target)
: "memory");
return ret;
}
static ALWAYS_INLINE unsigned long atomic_maxu(unsigned long *target, unsigned long value)
{
unsigned long ret;
__asm__ volatile("amomaxu.w.aq %0, %1, %2"
: "=r"(ret)
: "r"(value), "A"(*target)
: "memory");
return ret;
}
static ALWAYS_INLINE unsigned long atomic_minu(unsigned long *target, unsigned long value)
{
unsigned long ret;
__asm__ volatile("amominu.w.aq %0, %1, %2"
: "=r"(ret)
: "r"(value), "A"(*target)
: "memory");
return ret;
}
#endif /* CONFIG_64BIT */
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_ARCH_RISCV_ATOMIC_H_ */

View file

@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(riscv_atomic)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

View file

@ -0,0 +1,2 @@
CONFIG_ZTEST=y
CONFIG_MP_MAX_NUM_CPUS=1

View file

@ -0,0 +1,73 @@
/*
* Copyright (c) 2024 NextSilicon LTD
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/ztest.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/arch/riscv/atomic.h>
/**
* @brief Verify RISC-V specific atomic functionalities
* @details
* Test Objective:
* - Test if the RISC-V atomic instructions API is correct.
*
* Test Procedure:
* - Call the API interface of the following atomic operations in turn,
* decision is based on function return value and target operands.
* - atomic_swap()
* - atomic_max()
* - atomic_min()
*
* Expected Test Result:
* - The function return value and target operands are correct.
*
* Pass/Fail Criteria:
* - Successful if all check points in the test procedure have passed, failure otherwise.
*/
#define ATOMIC_WORD(val_if_64, val_if_32) \
((atomic_t)((sizeof(void *) == sizeof(uint64_t)) ? (val_if_64) : (val_if_32)))
ZTEST_USER(riscv_atomic, test_atomic)
{
atomic_t target;
atomic_val_t value;
unsigned long utarget, uvalue;
zassert_equal(sizeof(atomic_t), ATOMIC_WORD(sizeof(uint64_t), sizeof(uint32_t)),
"sizeof(atomic_t)");
/* atomic_swap */
target = 21;
value = 7;
zassert_true((atomic_swap(&target, value) == 21), "atomic_swap");
zassert_true((target == 7), "atomic_swap");
/* atomic_max */
target = 5;
value = -8;
zassert_true((atomic_max(&target, value) == 5), "atomic_max");
zassert_true((target == 5), "atomic_max");
/* atomic_min */
target = 5;
value = -8;
zassert_true((atomic_min(&target, value) == 5), "atomic_min");
zassert_true((target == -8), "atomic_min");
/* atomic_max unsigned */
utarget = 5;
uvalue = ATOMIC_WORD(0xffffffff00000000, 0xffff0000);
zassert_true((atomic_maxu(&utarget, uvalue) == 5), "atomic_maxu");
zassert_true((utarget == ATOMIC_WORD(0xffffffff00000000, 0xffff0000)), "atomic_maxu");
/* atomic_min unsigned */
utarget = 5;
uvalue = ATOMIC_WORD(0xffffffff00000000, 0xffff0000);
zassert_true((atomic_minu(&utarget, uvalue) == 5), "atomic_minu");
zassert_true((utarget == 5), "atomic_minu");
}
ZTEST_SUITE(riscv_atomic, NULL, NULL, NULL, NULL, NULL);

View file

@ -0,0 +1,4 @@
tests:
arch.riscv.atomic:
arch_allow: riscv
filter: CONFIG_RISCV_ISA_EXT_A and not CONFIG_SOC_OPENISA_RV32M1