c7e9729da6
CPP refers to the internal NFP Command Push Pull bus. This patch allows to create CPP commands from user space allowing to access any single part of the chip. This CPP interface is the base for having other functionalities like mutexes when accessing specific chip components, chip resources management, firmware upload or using the NSP, an embedded arm processor which can perform tasks on demand. NSP was the previous only way for doing things in the chip by the PMD, where a NSPU interface was used for commands like firmware upload or port link configuration. CPP interface supersedes NSPU, but it is still possible to use NSP through CPP. CPP interface adds a great flexibility for doing things like extended stats or firmware debugging. Signed-off-by: Alejandro Lucero <alejandro.lucero@netronome.com>
425 lines
9.7 KiB
C
425 lines
9.7 KiB
C
/* SPDX-License-Identifier: BSD-3-Clause
|
|
* Copyright(c) 2018 Netronome Systems, Inc.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include <malloc.h>
|
|
#include <time.h>
|
|
#include <sched.h>
|
|
|
|
#include "nfp_cpp.h"
|
|
#include "nfp6000/nfp6000.h"
|
|
|
|
#define MUTEX_LOCKED(interface) ((((uint32_t)(interface)) << 16) | 0x000f)
|
|
#define MUTEX_UNLOCK(interface) (0 | 0x0000)
|
|
|
|
#define MUTEX_IS_LOCKED(value) (((value) & 0xffff) == 0x000f)
|
|
#define MUTEX_IS_UNLOCKED(value) (((value) & 0xffff) == 0x0000)
|
|
#define MUTEX_INTERFACE(value) (((value) >> 16) & 0xffff)
|
|
|
|
/*
|
|
* If you need more than 65536 recursive locks, please
|
|
* rethink your code.
|
|
*/
|
|
#define MUTEX_DEPTH_MAX 0xffff
|
|
|
|
struct nfp_cpp_mutex {
|
|
struct nfp_cpp *cpp;
|
|
uint8_t target;
|
|
uint16_t depth;
|
|
unsigned long long address;
|
|
uint32_t key;
|
|
unsigned int usage;
|
|
struct nfp_cpp_mutex *prev, *next;
|
|
};
|
|
|
|
static int
|
|
_nfp_cpp_mutex_validate(uint32_t model, int *target, unsigned long long address)
|
|
{
|
|
/* Address must be 64-bit aligned */
|
|
if (address & 7)
|
|
return NFP_ERRNO(EINVAL);
|
|
|
|
if (NFP_CPP_MODEL_IS_6000(model)) {
|
|
if (*target != NFP_CPP_TARGET_MU)
|
|
return NFP_ERRNO(EINVAL);
|
|
} else {
|
|
return NFP_ERRNO(EINVAL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialize a mutex location
|
|
*
|
|
* The CPP target:address must point to a 64-bit aligned location, and
|
|
* will initialize 64 bits of data at the location.
|
|
*
|
|
* This creates the initial mutex state, as locked by this
|
|
* nfp_cpp_interface().
|
|
*
|
|
* This function should only be called when setting up
|
|
* the initial lock state upon boot-up of the system.
|
|
*
|
|
* @param mutex NFP CPP Mutex handle
|
|
* @param target NFP CPP target ID (ie NFP_CPP_TARGET_CLS or
|
|
* NFP_CPP_TARGET_MU)
|
|
* @param address Offset into the address space of the NFP CPP target ID
|
|
* @param key Unique 32-bit value for this mutex
|
|
*
|
|
* @return 0 on success, or -1 on failure (and set errno accordingly).
|
|
*/
|
|
int
|
|
nfp_cpp_mutex_init(struct nfp_cpp *cpp, int target, unsigned long long address,
|
|
uint32_t key)
|
|
{
|
|
uint32_t model = nfp_cpp_model(cpp);
|
|
uint32_t muw = NFP_CPP_ID(target, 4, 0); /* atomic_write */
|
|
int err;
|
|
|
|
err = _nfp_cpp_mutex_validate(model, &target, address);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = nfp_cpp_writel(cpp, muw, address + 4, key);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err =
|
|
nfp_cpp_writel(cpp, muw, address + 0,
|
|
MUTEX_LOCKED(nfp_cpp_interface(cpp)));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Create a mutex handle from an address controlled by a MU Atomic engine
|
|
*
|
|
* The CPP target:address must point to a 64-bit aligned location, and
|
|
* reserve 64 bits of data at the location for use by the handle.
|
|
*
|
|
* Only target/address pairs that point to entities that support the
|
|
* MU Atomic Engine are supported.
|
|
*
|
|
* @param cpp NFP CPP handle
|
|
* @param target NFP CPP target ID (ie NFP_CPP_TARGET_CLS or
|
|
* NFP_CPP_TARGET_MU)
|
|
* @param address Offset into the address space of the NFP CPP target ID
|
|
* @param key 32-bit unique key (must match the key at this location)
|
|
*
|
|
* @return A non-NULL struct nfp_cpp_mutex * on success, NULL on failure.
|
|
*/
|
|
struct nfp_cpp_mutex *
|
|
nfp_cpp_mutex_alloc(struct nfp_cpp *cpp, int target,
|
|
unsigned long long address, uint32_t key)
|
|
{
|
|
uint32_t model = nfp_cpp_model(cpp);
|
|
struct nfp_cpp_mutex *mutex;
|
|
uint32_t mur = NFP_CPP_ID(target, 3, 0); /* atomic_read */
|
|
int err;
|
|
uint32_t tmp;
|
|
|
|
/* Look for cached mutex */
|
|
for (mutex = cpp->mutex_cache; mutex; mutex = mutex->next) {
|
|
if (mutex->target == target && mutex->address == address)
|
|
break;
|
|
}
|
|
|
|
if (mutex) {
|
|
if (mutex->key == key) {
|
|
mutex->usage++;
|
|
return mutex;
|
|
}
|
|
|
|
/* If the key doesn't match... */
|
|
return NFP_ERRPTR(EEXIST);
|
|
}
|
|
|
|
err = _nfp_cpp_mutex_validate(model, &target, address);
|
|
if (err < 0)
|
|
return NULL;
|
|
|
|
err = nfp_cpp_readl(cpp, mur, address + 4, &tmp);
|
|
if (err < 0)
|
|
return NULL;
|
|
|
|
if (tmp != key)
|
|
return NFP_ERRPTR(EEXIST);
|
|
|
|
mutex = calloc(sizeof(*mutex), 1);
|
|
if (!mutex)
|
|
return NFP_ERRPTR(ENOMEM);
|
|
|
|
mutex->cpp = cpp;
|
|
mutex->target = target;
|
|
mutex->address = address;
|
|
mutex->key = key;
|
|
mutex->depth = 0;
|
|
mutex->usage = 1;
|
|
|
|
/* Add mutex to the cache */
|
|
if (cpp->mutex_cache) {
|
|
cpp->mutex_cache->prev = mutex;
|
|
mutex->next = cpp->mutex_cache;
|
|
cpp->mutex_cache = mutex;
|
|
} else {
|
|
cpp->mutex_cache = mutex;
|
|
}
|
|
|
|
return mutex;
|
|
}
|
|
|
|
struct nfp_cpp *
|
|
nfp_cpp_mutex_cpp(struct nfp_cpp_mutex *mutex)
|
|
{
|
|
return mutex->cpp;
|
|
}
|
|
|
|
uint32_t
|
|
nfp_cpp_mutex_key(struct nfp_cpp_mutex *mutex)
|
|
{
|
|
return mutex->key;
|
|
}
|
|
|
|
uint16_t
|
|
nfp_cpp_mutex_owner(struct nfp_cpp_mutex *mutex)
|
|
{
|
|
uint32_t mur = NFP_CPP_ID(mutex->target, 3, 0); /* atomic_read */
|
|
uint32_t value, key;
|
|
int err;
|
|
|
|
err = nfp_cpp_readl(mutex->cpp, mur, mutex->address, &value);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = nfp_cpp_readl(mutex->cpp, mur, mutex->address + 4, &key);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (key != mutex->key)
|
|
return NFP_ERRNO(EPERM);
|
|
|
|
if (!MUTEX_IS_LOCKED(value))
|
|
return 0;
|
|
|
|
return MUTEX_INTERFACE(value);
|
|
}
|
|
|
|
int
|
|
nfp_cpp_mutex_target(struct nfp_cpp_mutex *mutex)
|
|
{
|
|
return mutex->target;
|
|
}
|
|
|
|
uint64_t
|
|
nfp_cpp_mutex_address(struct nfp_cpp_mutex *mutex)
|
|
{
|
|
return mutex->address;
|
|
}
|
|
|
|
/*
|
|
* Free a mutex handle - does not alter the lock state
|
|
*
|
|
* @param mutex NFP CPP Mutex handle
|
|
*/
|
|
void
|
|
nfp_cpp_mutex_free(struct nfp_cpp_mutex *mutex)
|
|
{
|
|
mutex->usage--;
|
|
if (mutex->usage > 0)
|
|
return;
|
|
|
|
/* Remove mutex from the cache */
|
|
if (mutex->next)
|
|
mutex->next->prev = mutex->prev;
|
|
if (mutex->prev)
|
|
mutex->prev->next = mutex->next;
|
|
|
|
/* If mutex->cpp == NULL, something broke */
|
|
if (mutex->cpp && mutex == mutex->cpp->mutex_cache)
|
|
mutex->cpp->mutex_cache = mutex->next;
|
|
|
|
free(mutex);
|
|
}
|
|
|
|
/*
|
|
* Lock a mutex handle, using the NFP MU Atomic Engine
|
|
*
|
|
* @param mutex NFP CPP Mutex handle
|
|
*
|
|
* @return 0 on success, or -1 on failure (and set errno accordingly).
|
|
*/
|
|
int
|
|
nfp_cpp_mutex_lock(struct nfp_cpp_mutex *mutex)
|
|
{
|
|
int err;
|
|
time_t warn_at = time(NULL) + 15;
|
|
|
|
while ((err = nfp_cpp_mutex_trylock(mutex)) != 0) {
|
|
/* If errno != EBUSY, then the lock was damaged */
|
|
if (err < 0 && errno != EBUSY)
|
|
return err;
|
|
if (time(NULL) >= warn_at) {
|
|
printf("Warning: waiting for NFP mutex\n");
|
|
printf("\tusage:%u\n", mutex->usage);
|
|
printf("\tdepth:%hd]\n", mutex->depth);
|
|
printf("\ttarget:%d\n", mutex->target);
|
|
printf("\taddr:%llx\n", mutex->address);
|
|
printf("\tkey:%08x]\n", mutex->key);
|
|
warn_at = time(NULL) + 60;
|
|
}
|
|
sched_yield();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Unlock a mutex handle, using the NFP MU Atomic Engine
|
|
*
|
|
* @param mutex NFP CPP Mutex handle
|
|
*
|
|
* @return 0 on success, or -1 on failure (and set errno accordingly).
|
|
*/
|
|
int
|
|
nfp_cpp_mutex_unlock(struct nfp_cpp_mutex *mutex)
|
|
{
|
|
uint32_t muw = NFP_CPP_ID(mutex->target, 4, 0); /* atomic_write */
|
|
uint32_t mur = NFP_CPP_ID(mutex->target, 3, 0); /* atomic_read */
|
|
struct nfp_cpp *cpp = mutex->cpp;
|
|
uint32_t key, value;
|
|
uint16_t interface = nfp_cpp_interface(cpp);
|
|
int err;
|
|
|
|
if (mutex->depth > 1) {
|
|
mutex->depth--;
|
|
return 0;
|
|
}
|
|
|
|
err = nfp_cpp_readl(mutex->cpp, mur, mutex->address, &value);
|
|
if (err < 0)
|
|
goto exit;
|
|
|
|
err = nfp_cpp_readl(mutex->cpp, mur, mutex->address + 4, &key);
|
|
if (err < 0)
|
|
goto exit;
|
|
|
|
if (key != mutex->key) {
|
|
err = NFP_ERRNO(EPERM);
|
|
goto exit;
|
|
}
|
|
|
|
if (value != MUTEX_LOCKED(interface)) {
|
|
err = NFP_ERRNO(EACCES);
|
|
goto exit;
|
|
}
|
|
|
|
err = nfp_cpp_writel(cpp, muw, mutex->address, MUTEX_UNLOCK(interface));
|
|
if (err < 0)
|
|
goto exit;
|
|
|
|
mutex->depth = 0;
|
|
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Attempt to lock a mutex handle, using the NFP MU Atomic Engine
|
|
*
|
|
* Valid lock states:
|
|
*
|
|
* 0x....0000 - Unlocked
|
|
* 0x....000f - Locked
|
|
*
|
|
* @param mutex NFP CPP Mutex handle
|
|
* @return 0 if the lock succeeded, -1 on failure (and errno set
|
|
* appropriately).
|
|
*/
|
|
int
|
|
nfp_cpp_mutex_trylock(struct nfp_cpp_mutex *mutex)
|
|
{
|
|
uint32_t mur = NFP_CPP_ID(mutex->target, 3, 0); /* atomic_read */
|
|
uint32_t muw = NFP_CPP_ID(mutex->target, 4, 0); /* atomic_write */
|
|
uint32_t mus = NFP_CPP_ID(mutex->target, 5, 3); /* test_set_imm */
|
|
uint32_t key, value, tmp;
|
|
struct nfp_cpp *cpp = mutex->cpp;
|
|
int err;
|
|
|
|
if (mutex->depth > 0) {
|
|
if (mutex->depth == MUTEX_DEPTH_MAX)
|
|
return NFP_ERRNO(E2BIG);
|
|
|
|
mutex->depth++;
|
|
return 0;
|
|
}
|
|
|
|
/* Verify that the lock marker is not damaged */
|
|
err = nfp_cpp_readl(cpp, mur, mutex->address + 4, &key);
|
|
if (err < 0)
|
|
goto exit;
|
|
|
|
if (key != mutex->key) {
|
|
err = NFP_ERRNO(EPERM);
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* Compare against the unlocked state, and if true,
|
|
* write the interface id into the top 16 bits, and
|
|
* mark as locked.
|
|
*/
|
|
value = MUTEX_LOCKED(nfp_cpp_interface(cpp));
|
|
|
|
/*
|
|
* We use test_set_imm here, as it implies a read
|
|
* of the current state, and sets the bits in the
|
|
* bytemask of the command to 1s. Since the mutex
|
|
* is guaranteed to be 64-bit aligned, the bytemask
|
|
* of this 32-bit command is ensured to be 8'b00001111,
|
|
* which implies that the lower 4 bits will be set to
|
|
* ones regardless of the initial state.
|
|
*
|
|
* Since this is a 'Readback' operation, with no Pull
|
|
* data, we can treat this as a normal Push (read)
|
|
* atomic, which returns the original value.
|
|
*/
|
|
err = nfp_cpp_readl(cpp, mus, mutex->address, &tmp);
|
|
if (err < 0)
|
|
goto exit;
|
|
|
|
/* Was it unlocked? */
|
|
if (MUTEX_IS_UNLOCKED(tmp)) {
|
|
/*
|
|
* The read value can only be 0x....0000 in the unlocked state.
|
|
* If there was another contending for this lock, then
|
|
* the lock state would be 0x....000f
|
|
*
|
|
* Write our owner ID into the lock
|
|
* While not strictly necessary, this helps with
|
|
* debug and bookkeeping.
|
|
*/
|
|
err = nfp_cpp_writel(cpp, muw, mutex->address, value);
|
|
if (err < 0)
|
|
goto exit;
|
|
|
|
mutex->depth = 1;
|
|
goto exit;
|
|
}
|
|
|
|
/* Already locked by us? Success! */
|
|
if (tmp == value) {
|
|
mutex->depth = 1;
|
|
goto exit;
|
|
}
|
|
|
|
err = NFP_ERRNO(MUTEX_IS_LOCKED(tmp) ? EBUSY : EINVAL);
|
|
|
|
exit:
|
|
return err;
|
|
}
|