diff --git a/env/posix/ocf_env.h b/env/posix/ocf_env.h index 6acc898f3..25084f0ba 100644 --- a/env/posix/ocf_env.h +++ b/env/posix/ocf_env.h @@ -1,6 +1,4 @@ /* - * Copyright(c) 2019-2022 Intel Corporation - * Copyright(c) 2023-2025 Huawei Technologies * Copyright(c) 2026 Unvertical * SPDX-License-Identifier: BSD-3-Clause */ @@ -8,699 +6,31 @@ #ifndef __OCF_ENV_H__ #define __OCF_ENV_H__ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#ifndef __USE_GNU -#define __USE_GNU -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ocf_env_list.h" -#include "ocf_env_headers.h" -#include "ocf/ocf_err.h" -#include "utils_mpool.h" - -/* linux sector 512-bytes */ -#define ENV_SECTOR_SHIFT 9 - -#define OCF_ALLOCATOR_NAME_MAX 128 - -#define PAGE_SIZE 4096 - -#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) -#define min(a,b) MIN(a,b) - -#define ENV_PRIu64 "lu" -#define ENV_PRId64 "ld" - -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef uint64_t u64; - -typedef uint64_t sector_t; - -#define __packed __attribute__((packed)) - -#define likely(cond) __builtin_expect(!!(cond), 1) -#define unlikely(cond) __builtin_expect(!!(cond), 0) - -/* MEMORY MANAGEMENT */ -#define ENV_MEM_NORMAL 0 -#define ENV_MEM_NOIO 0 -#define ENV_MEM_ATOMIC 0 - -/* DEBUGING */ -void env_stack_trace(void); - -#define ENV_WARN(cond, fmt...) printf(fmt) -#define ENV_WARN_ON(cond) ; -#define ENV_WARN_ONCE(cond, fmt...) ENV_WARN(cond, fmt) - -#define ENV_BUG() do {env_stack_trace(); assert(0);} while(0) -#define ENV_BUG_ON(cond) do { if (cond) ENV_BUG(); } while (0) -#define ENV_BUILD_BUG_ON(cond) _Static_assert(!(cond), "static "\ - "assertion failure") - -/* MISC UTILITIES */ -#define container_of(ptr, type, member) ({ \ - const typeof(((type *)0)->member)*__mptr = (ptr); \ - (type *)((char *)__mptr - offsetof(type, member)); }) - -#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) - -/* STRING OPERATIONS */ -#define env_memcpy(dest, dmax, src, slen) ({ \ - memcpy(dest, src, min(dmax, slen)); \ - 0; \ - }) -#define env_memset(dest, dmax, val) ({ \ - memset(dest, val, dmax); \ - 0; \ - }) -#define env_memcmp(s1, s1max, s2, s2max, diff) ({ \ - *diff = memcmp(s1, s2, min(s1max, s2max)); \ - 0; \ - }) -#define env_strdup strndup -#define env_strnlen(s, smax) strnlen(s, smax) -#define env_strncmp(s1, slen1, s2, slen2) strncmp(s1, s2, min(slen1, slen2)) -#define env_strncpy(dest, dmax, src, slen) ({ \ - strncpy(dest, src, min(dmax - 1, slen)); \ - dest[dmax - 1] = '\0'; \ - 0; \ - }) - -/* MEMORY MANAGEMENT */ -static inline void *env_malloc(size_t size, int flags) -{ - return malloc(size); -} - -static inline void *env_zalloc(size_t size, int flags) -{ - void *ptr = malloc(size); - - if (ptr) - memset(ptr, 0, size); - - return ptr; -} - -static inline void env_free(const void *ptr) -{ - free((void *)ptr); -} - -static inline void *env_vmalloc_flags(size_t size, int flags) -{ - return malloc(size); -} - -static inline void *env_vzalloc_flags(size_t size, int flags) -{ - return env_zalloc(size, 0); -} - -static inline void *env_vmalloc(size_t size) -{ - return malloc(size); -} - -static inline void *env_vzalloc(size_t size) -{ - return env_zalloc(size, 0); -} - -static inline void env_vfree(const void *ptr) -{ - free((void *)ptr); -} - -/* SECURE MEMORY MANAGEMENT */ /* - * OCF adapter can opt to take additional steps to securely allocate and free - * memory used by OCF to store cache metadata. This is to prevent other - * entities in the system from acquiring parts of OCF cache metadata via - * memory allocations. If this is not a concern in given product, secure - * alloc/free should default to vmalloc/vfree. + * Default posix environment configuration. * - * Memory returned from secure alloc is not expected to be physically continous - * nor zeroed. + * Each section in ocf_env_default.h is guarded by an OCF_ENV_POSIX_* + * define. To replace a section with a custom implementation, remove the + * corresponding define below and provide your own definitions. */ -/* default to standard memory allocations for secure allocations */ -#define SECURE_MEMORY_HANDLING 0 - -static inline void *env_secure_alloc(size_t size) -{ - void *ptr = malloc(size); - -#if SECURE_MEMORY_HANDLING - if (ptr && mlock(ptr, size)) { - free(ptr); - ptr = NULL; - } -#endif - - return ptr; -} - -static inline void env_secure_free(const void *ptr, size_t size) -{ - if (ptr) { -#if SECURE_MEMORY_HANDLING - memset(ptr, 0, size); - /* TODO: flush CPU caches ? */ - ENV_BUG_ON(munlock(ptr)); -#endif - free((void*)ptr); - } -} - -static inline uint64_t env_get_free_memory(void) -{ - return (uint64_t)(-1); -} - -/* ALLOCATOR */ -typedef struct _env_allocator env_allocator; - -env_allocator *env_allocator_create(uint32_t size, const char *name, bool zero); - -#define env_allocator_create_extended(size, name, limit, zero) \ - env_allocator_create(size, name, zero) - -void env_allocator_destroy(env_allocator *allocator); - -void *env_allocator_new(env_allocator *allocator); - -void env_allocator_del(env_allocator *allocator, void *item); - -/* MUTEX */ -typedef struct { - pthread_mutex_t m; -} env_mutex; - -#define env_cond_resched() ({}) - -static inline int env_mutex_init(env_mutex *mutex) -{ - if(pthread_mutex_init(&mutex->m, NULL)) - return 1; - - return 0; -} - -static inline void env_mutex_lock(env_mutex *mutex) -{ - ENV_BUG_ON(pthread_mutex_lock(&mutex->m)); -} - -static inline int env_mutex_trylock(env_mutex *mutex) -{ - return pthread_mutex_trylock(&mutex->m); -} - -static inline int env_mutex_lock_interruptible(env_mutex *mutex) -{ - env_mutex_lock(mutex); - return 0; -} - -static inline void env_mutex_unlock(env_mutex *mutex) -{ - ENV_BUG_ON(pthread_mutex_unlock(&mutex->m)); -} - -static inline int env_mutex_destroy(env_mutex *mutex) -{ - if(pthread_mutex_destroy(&mutex->m)) - return 1; - - return 0; -} - -/* RECURSIVE MUTEX */ -typedef env_mutex env_rmutex; - -static inline int env_rmutex_init(env_rmutex *rmutex) -{ - pthread_mutexattr_t attr; - - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(&rmutex->m, &attr); - - return 0; -} - -static inline void env_rmutex_lock(env_rmutex *rmutex) -{ - env_mutex_lock(rmutex); -} - -static inline int env_rmutex_lock_interruptible(env_rmutex *rmutex) -{ - return env_mutex_lock_interruptible(rmutex); -} - -static inline void env_rmutex_unlock(env_rmutex *rmutex) -{ - env_mutex_unlock(rmutex); -} - -static inline int env_rmutex_destroy(env_rmutex *rmutex) -{ - if(pthread_mutex_destroy(&rmutex->m)) - return 1; - - return 0; -} - -/* RW SEMAPHORE */ -typedef struct { - pthread_rwlock_t lock; -} env_rwsem; - -static inline int env_rwsem_init(env_rwsem *s) -{ - return pthread_rwlock_init(&s->lock, NULL); -} - -static inline void env_rwsem_up_read(env_rwsem *s) -{ - ENV_BUG_ON(pthread_rwlock_unlock(&s->lock)); -} - -static inline void env_rwsem_down_read(env_rwsem *s) -{ - ENV_BUG_ON(pthread_rwlock_rdlock(&s->lock)); -} - -static inline int env_rwsem_down_read_trylock(env_rwsem *s) -{ - return pthread_rwlock_tryrdlock(&s->lock) ? -OCF_ERR_NO_LOCK : 0; -} - -static inline void env_rwsem_up_write(env_rwsem *s) -{ - ENV_BUG_ON(pthread_rwlock_unlock(&s->lock)); -} - -static inline void env_rwsem_down_write(env_rwsem *s) -{ - ENV_BUG_ON(pthread_rwlock_wrlock(&s->lock)); -} - -static inline int env_rwsem_down_write_trylock(env_rwsem *s) -{ - return pthread_rwlock_trywrlock(&s->lock) ? -OCF_ERR_NO_LOCK : 0; -} - -static inline int env_rwsem_destroy(env_rwsem *s) -{ - return pthread_rwlock_destroy(&s->lock); -} - -/* COMPLETION */ -struct completion { - sem_t sem; -}; - -typedef struct completion env_completion; - -static inline void env_completion_init(env_completion *completion) -{ - sem_init(&completion->sem, 0, 0); -} - -static inline void env_completion_wait(env_completion *completion) -{ - sem_wait(&completion->sem); -} - -static inline void env_completion_complete(env_completion *completion) -{ - sem_post(&completion->sem); -} - -static inline void env_completion_destroy(env_completion *completion) -{ - sem_destroy(&completion->sem); -} - -/* ATOMIC VARIABLES */ - -typedef struct { - volatile uint8_t counter; -} env_atomic8; - -static inline uint8_t env_atomic8_read(const env_atomic8 *a) -{ - return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); -} - -static inline void env_atomic8_set(env_atomic8 *a, uint8_t i) -{ - __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); -} - -static inline uint8_t env_atomic8_cmpxchg(env_atomic8 *a, - uint8_t old, uint8_t new_value) -{ - return __sync_val_compare_and_swap(&a->counter, old, new_value); -} - -static inline void env_atomic8_sub(uint8_t i, env_atomic8 *a) -{ - __sync_sub_and_fetch(&a->counter, i); -} - -static inline void env_atomic8_dec(env_atomic8 *a) -{ - env_atomic8_sub(1, a); -} - -static inline uint8_t env_atomic8_add_unless(env_atomic8 *a, - uint8_t i, uint8_t u) -{ - uint8_t c, old; - - c = env_atomic8_read(a); - for (;;) { - if (unlikely(c == (u))) - break; - old = env_atomic8_cmpxchg((a), c, c + (i)); - if (likely(old == c)) - break; - c = old; - } - return c != (u); -} - -typedef struct { - volatile int counter; -} env_atomic; - -static inline int env_atomic_read(const env_atomic *a) -{ - return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); -} - -static inline void env_atomic_set(env_atomic *a, int i) -{ - __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); -} - -static inline void env_atomic_add(int i, env_atomic *a) -{ - __sync_add_and_fetch(&a->counter, i); -} - -static inline void env_atomic_sub(int i, env_atomic *a) -{ - __sync_sub_and_fetch(&a->counter, i); -} - -static inline void env_atomic_inc(env_atomic *a) -{ - env_atomic_add(1, a); -} - -static inline void env_atomic_dec(env_atomic *a) -{ - env_atomic_sub(1, a); -} - -static inline bool env_atomic_dec_and_test(env_atomic *a) -{ - return __sync_sub_and_fetch(&a->counter, 1) == 0; -} - -static inline int env_atomic_add_return(int i, env_atomic *a) -{ - return __sync_add_and_fetch(&a->counter, i); -} - -static inline int env_atomic_sub_return(int i, env_atomic *a) -{ - return __sync_sub_and_fetch(&a->counter, i); -} - -static inline int env_atomic_inc_return(env_atomic *a) -{ - return env_atomic_add_return(1, a); -} - -static inline int env_atomic_dec_return(env_atomic *a) -{ - return env_atomic_sub_return(1, a); -} - -static inline int env_atomic_cmpxchg(env_atomic *a, int old, int new_value) -{ - return __sync_val_compare_and_swap(&a->counter, old, new_value); -} - -static inline int env_atomic_add_unless(env_atomic *a, int i, int u) -{ - int c, old; - c = env_atomic_read(a); - for (;;) { - if (unlikely(c == (u))) - break; - old = env_atomic_cmpxchg((a), c, c + (i)); - if (likely(old == c)) - break; - c = old; - } - return c != (u); -} - -typedef struct { - volatile long counter; -} env_atomic64; - -static inline long env_atomic64_read(const env_atomic64 *a) -{ - return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); -} - -static inline void env_atomic64_set(env_atomic64 *a, long i) -{ - __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); -} - -static inline void env_atomic64_add(long i, env_atomic64 *a) -{ - __sync_add_and_fetch(&a->counter, i); -} - -static inline void env_atomic64_sub(long i, env_atomic64 *a) -{ - __sync_sub_and_fetch(&a->counter, i); -} - -static inline void env_atomic64_inc(env_atomic64 *a) -{ - env_atomic64_add(1, a); -} - -static inline void env_atomic64_dec(env_atomic64 *a) -{ - env_atomic64_sub(1, a); -} - -static inline long env_atomic64_inc_return(env_atomic64 *a) -{ - return __sync_add_and_fetch(&a->counter, 1); -} - -static inline long env_atomic64_cmpxchg(env_atomic64 *a, long old_v, long new_v) -{ - return __sync_val_compare_and_swap(&a->counter, old_v, new_v); -} - -/* SPIN LOCKS */ -typedef struct { - pthread_spinlock_t lock; -} env_spinlock; - -static inline int env_spinlock_init(env_spinlock *l) -{ - return pthread_spin_init(&l->lock, 0); -} - -static inline int env_spinlock_trylock(env_spinlock *l) -{ - return pthread_spin_trylock(&l->lock) ? -OCF_ERR_NO_LOCK : 0; -} - -static inline void env_spinlock_lock(env_spinlock *l) -{ - ENV_BUG_ON(pthread_spin_lock(&l->lock)); -} - -static inline void env_spinlock_unlock(env_spinlock *l) -{ - ENV_BUG_ON(pthread_spin_unlock(&l->lock)); -} - -#define env_spinlock_lock_irqsave(l, flags) \ - (void)flags; \ - env_spinlock_lock(l) - -#define env_spinlock_unlock_irqrestore(l, flags) \ - (void)flags; \ - env_spinlock_unlock(l) - -static inline void env_spinlock_destroy(env_spinlock *l) -{ - ENV_BUG_ON(pthread_spin_destroy(&l->lock)); -} - -/* RW LOCKS */ -typedef struct { - pthread_rwlock_t lock; -} env_rwlock; - -static inline void env_rwlock_init(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_init(&l->lock, NULL)); -} - -static inline void env_rwlock_read_lock(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_rdlock(&l->lock)); -} - -static inline void env_rwlock_read_unlock(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_unlock(&l->lock)); -} - -static inline void env_rwlock_write_lock(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_wrlock(&l->lock)); -} - -static inline void env_rwlock_write_unlock(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_unlock(&l->lock)); -} - -static inline void env_rwlock_destroy(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_destroy(&l->lock)); -} - -/* BIT OPERATIONS */ -static inline void env_bit_set(int nr, volatile void *addr) -{ - char *byte = (char *)addr + (nr >> 3); - char mask = 1 << (nr & 7); - - __sync_or_and_fetch(byte, mask); -} - -static inline void env_bit_clear(int nr, volatile void *addr) -{ - char *byte = (char *)addr + (nr >> 3); - char mask = 1 << (nr & 7); - - mask = ~mask; - __sync_and_and_fetch(byte, mask); -} - -static inline bool env_bit_test(int nr, const volatile unsigned long *addr) -{ - const char *byte = (char *)addr + (nr >> 3); - char mask = 1 << (nr & 7); - - __sync_synchronize(); - return !!(*byte & mask); -} - -/* SCHEDULING */ -static inline int env_in_interrupt(void) -{ - return 0; -} - -/* TIME */ -#define ENV_SEC_TO_NSEC(_sec) ((_sec) * 1000000000) -#define ENV_NSEC_TO_SEC(_sec) ((_sec) / 1000000000) -#define ENV_NSEC_TO_MSEC(_sec) ((_sec) / 1000000) - -static inline uint64_t env_get_tick_count(void) -{ - struct timespec tv; - - return clock_gettime(CLOCK_REALTIME, &tv) ? - 0 : ENV_SEC_TO_NSEC(tv.tv_sec) + tv.tv_nsec; -} - -static inline uint64_t env_ticks_to_nsecs(uint64_t j) -{ - return j; -} - -static inline uint64_t env_ticks_to_msecs(uint64_t j) -{ - return ENV_NSEC_TO_MSEC(j); -} - -static inline uint64_t env_ticks_to_secs(uint64_t j) -{ - return ENV_NSEC_TO_SEC(j); -} - -static inline uint64_t env_secs_to_ticks(uint64_t j) -{ - return ENV_SEC_TO_NSEC(j); -} - -/* SORTING */ -static inline void env_sort(void *base, size_t num, size_t size, - int (*cmp_fn)(const void *, const void *), - void (*swap_fn)(void *, void *, int size)) -{ - qsort(base, num, size, cmp_fn); -} - -/* TIME */ -static inline void env_msleep(uint64_t n) -{ - usleep(n * 1000); -} - -struct env_timeval { - uint64_t sec, usec; -}; - -uint32_t env_crc32(uint32_t crc, uint8_t const *data, size_t len); - -unsigned env_get_execution_context(void); -void env_put_execution_context(unsigned ctx); -unsigned env_get_execution_context_count(void); +#define OCF_ENV_POSIX_DEBUG +#define OCF_ENV_POSIX_STRING +#define OCF_ENV_POSIX_MEMORY +#define OCF_ENV_POSIX_MUTEX +#define OCF_ENV_POSIX_RMUTEX +#define OCF_ENV_POSIX_RWSEM +#define OCF_ENV_POSIX_COMPLETION +#define OCF_ENV_POSIX_ATOMIC +#define OCF_ENV_POSIX_SPINLOCK +#define OCF_ENV_POSIX_RWLOCK +#define OCF_ENV_POSIX_BIT +#define OCF_ENV_POSIX_SCHEDULING +#define OCF_ENV_POSIX_TIME +#define OCF_ENV_POSIX_SORTING +#define OCF_ENV_POSIX_CRC +#define OCF_ENV_POSIX_EXECUTION_CONTEXT + +#include "ocf_env_default.h" #endif /* __OCF_ENV_H__ */ diff --git a/env/posix/ocf_env_default.h b/env/posix/ocf_env_default.h new file mode 100644 index 000000000..956a27dcb --- /dev/null +++ b/env/posix/ocf_env_default.h @@ -0,0 +1,770 @@ +/* + * Copyright(c) 2019-2022 Intel Corporation + * Copyright(c) 2023-2025 Huawei Technologies + * Copyright(c) 2026 Unvertical + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __OCF_ENV_DEFAULT_H__ +#define __OCF_ENV_DEFAULT_H__ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#ifndef __USE_GNU +#define __USE_GNU +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ocf_env_list.h" +#include "ocf_env_headers.h" +#include "ocf/ocf_err.h" +#include "utils_mpool.h" + +/* TYPES */ +/* linux sector 512-bytes */ +#define ENV_SECTOR_SHIFT 9 + +#define OCF_ALLOCATOR_NAME_MAX 128 + +#define PAGE_SIZE 4096 + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +#define min(a,b) MIN(a,b) + +#define ENV_PRIu64 "lu" +#define ENV_PRId64 "ld" + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef uint64_t sector_t; + +#define __packed __attribute__((packed)) + +#define likely(cond) __builtin_expect(!!(cond), 1) +#define unlikely(cond) __builtin_expect(!!(cond), 0) + +/* MISC UTILITIES */ +#define container_of(ptr, type, member) ({ \ + const typeof(((type *)0)->member)*__mptr = (ptr); \ + (type *)((char *)__mptr - offsetof(type, member)); }) + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) + +/* DEBUG */ +#ifdef OCF_ENV_POSIX_DEBUG + +void env_stack_trace(void); + +#define ENV_WARN(cond, fmt...) printf(fmt) +#define ENV_WARN_ON(cond) ; +#define ENV_WARN_ONCE(cond, fmt...) ENV_WARN(cond, fmt) + +#define ENV_BUG() do {env_stack_trace(); assert(0);} while(0) +#define ENV_BUG_ON(cond) do { if (cond) ENV_BUG(); } while (0) +#define ENV_BUILD_BUG_ON(cond) _Static_assert(!(cond), "static "\ + "assertion failure") + +#endif /* OCF_ENV_POSIX_DEBUG */ + +/* STRING */ +#ifdef OCF_ENV_POSIX_STRING + +#define env_memcpy(dest, dmax, src, slen) ({ \ + memcpy(dest, src, min(dmax, slen)); \ + 0; \ + }) +#define env_memset(dest, dmax, val) ({ \ + memset(dest, val, dmax); \ + 0; \ + }) +#define env_memcmp(s1, s1max, s2, s2max, diff) ({ \ + *diff = memcmp(s1, s2, min(s1max, s2max)); \ + 0; \ + }) +#define env_strdup strndup +#define env_strnlen(s, smax) strnlen(s, smax) +#define env_strncmp(s1, slen1, s2, slen2) strncmp(s1, s2, min(slen1, slen2)) +#define env_strncpy(dest, dmax, src, slen) ({ \ + strncpy(dest, src, min(dmax - 1, slen)); \ + dest[dmax - 1] = '\0'; \ + 0; \ + }) + +#endif /* OCF_ENV_POSIX_STRING */ + +/* MEMORY */ +#ifdef OCF_ENV_POSIX_MEMORY + +#define ENV_MEM_NORMAL 0 +#define ENV_MEM_NOIO 0 +#define ENV_MEM_ATOMIC 0 + +static inline void *env_malloc(size_t size, int flags) +{ + return malloc(size); +} + +static inline void *env_zalloc(size_t size, int flags) +{ + void *ptr = malloc(size); + + if (ptr) + memset(ptr, 0, size); + + return ptr; +} + +static inline void env_free(const void *ptr) +{ + free((void *)ptr); +} + +static inline void *env_vmalloc_flags(size_t size, int flags) +{ + return malloc(size); +} + +static inline void *env_vzalloc_flags(size_t size, int flags) +{ + return env_zalloc(size, 0); +} + +static inline void *env_vmalloc(size_t size) +{ + return malloc(size); +} + +static inline void *env_vzalloc(size_t size) +{ + return env_zalloc(size, 0); +} + +static inline void env_vfree(const void *ptr) +{ + free((void *)ptr); +} + +/* SECURE MEMORY */ +/* + * OCF adapter can opt to take additional steps to securely allocate and free + * memory used by OCF to store cache metadata. This is to prevent other + * entities in the system from acquiring parts of OCF cache metadata via + * memory allocations. If this is not a concern in given product, secure + * alloc/free should default to vmalloc/vfree. + * + * Memory returned from secure alloc is not expected to be physically continous + * nor zeroed. + */ + +/* default to standard memory allocations for secure allocations */ +#define SECURE_MEMORY_HANDLING 0 + +static inline void *env_secure_alloc(size_t size) +{ + void *ptr = malloc(size); + +#if SECURE_MEMORY_HANDLING + if (ptr && mlock(ptr, size)) { + free(ptr); + ptr = NULL; + } +#endif + + return ptr; +} + +static inline void env_secure_free(const void *ptr, size_t size) +{ + if (ptr) { +#if SECURE_MEMORY_HANDLING + memset(ptr, 0, size); + /* TODO: flush CPU caches ? */ + ENV_BUG_ON(munlock(ptr)); +#endif + free((void*)ptr); + } +} + +static inline uint64_t env_get_free_memory(void) +{ + return (uint64_t)(-1); +} + +/* ALLOCATOR */ +typedef struct _env_allocator env_allocator; + +env_allocator *env_allocator_create(uint32_t size, const char *name, bool zero); + +#define env_allocator_create_extended(size, name, limit, zero) \ + env_allocator_create(size, name, zero) + +void env_allocator_destroy(env_allocator *allocator); + +void *env_allocator_new(env_allocator *allocator); + +void env_allocator_del(env_allocator *allocator, void *item); + +#endif /* OCF_ENV_POSIX_MEMORY */ + +/* MUTEX */ +#ifdef OCF_ENV_POSIX_MUTEX + +typedef struct { + pthread_mutex_t m; +} env_mutex; + +#define env_cond_resched() ({}) + +static inline int env_mutex_init(env_mutex *mutex) +{ + if(pthread_mutex_init(&mutex->m, NULL)) + return 1; + + return 0; +} + +static inline void env_mutex_lock(env_mutex *mutex) +{ + ENV_BUG_ON(pthread_mutex_lock(&mutex->m)); +} + +static inline int env_mutex_trylock(env_mutex *mutex) +{ + return pthread_mutex_trylock(&mutex->m); +} + +static inline int env_mutex_lock_interruptible(env_mutex *mutex) +{ + env_mutex_lock(mutex); + return 0; +} + +static inline void env_mutex_unlock(env_mutex *mutex) +{ + ENV_BUG_ON(pthread_mutex_unlock(&mutex->m)); +} + +static inline int env_mutex_destroy(env_mutex *mutex) +{ + if(pthread_mutex_destroy(&mutex->m)) + return 1; + + return 0; +} + +#endif /* OCF_ENV_POSIX_MUTEX */ + +/* RECURSIVE MUTEX */ +#ifdef OCF_ENV_POSIX_RMUTEX + +typedef env_mutex env_rmutex; + +static inline int env_rmutex_init(env_rmutex *rmutex) +{ + pthread_mutexattr_t attr; + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&rmutex->m, &attr); + + return 0; +} + +static inline void env_rmutex_lock(env_rmutex *rmutex) +{ + env_mutex_lock(rmutex); +} + +static inline int env_rmutex_lock_interruptible(env_rmutex *rmutex) +{ + return env_mutex_lock_interruptible(rmutex); +} + +static inline void env_rmutex_unlock(env_rmutex *rmutex) +{ + env_mutex_unlock(rmutex); +} + +static inline int env_rmutex_destroy(env_rmutex *rmutex) +{ + if(pthread_mutex_destroy(&rmutex->m)) + return 1; + + return 0; +} + +#endif /* OCF_ENV_POSIX_RMUTEX */ + +/* RWSEM */ +#ifdef OCF_ENV_POSIX_RWSEM + +typedef struct { + pthread_rwlock_t lock; +} env_rwsem; + +static inline int env_rwsem_init(env_rwsem *s) +{ + return pthread_rwlock_init(&s->lock, NULL); +} + +static inline void env_rwsem_up_read(env_rwsem *s) +{ + ENV_BUG_ON(pthread_rwlock_unlock(&s->lock)); +} + +static inline void env_rwsem_down_read(env_rwsem *s) +{ + ENV_BUG_ON(pthread_rwlock_rdlock(&s->lock)); +} + +static inline int env_rwsem_down_read_trylock(env_rwsem *s) +{ + return pthread_rwlock_tryrdlock(&s->lock) ? -OCF_ERR_NO_LOCK : 0; +} + +static inline void env_rwsem_up_write(env_rwsem *s) +{ + ENV_BUG_ON(pthread_rwlock_unlock(&s->lock)); +} + +static inline void env_rwsem_down_write(env_rwsem *s) +{ + ENV_BUG_ON(pthread_rwlock_wrlock(&s->lock)); +} + +static inline int env_rwsem_down_write_trylock(env_rwsem *s) +{ + return pthread_rwlock_trywrlock(&s->lock) ? -OCF_ERR_NO_LOCK : 0; +} + +static inline int env_rwsem_destroy(env_rwsem *s) +{ + return pthread_rwlock_destroy(&s->lock); +} + +#endif /* OCF_ENV_POSIX_RWSEM */ + +/* COMPLETION */ +#ifdef OCF_ENV_POSIX_COMPLETION + +struct completion { + sem_t sem; +}; + +typedef struct completion env_completion; + +static inline void env_completion_init(env_completion *completion) +{ + sem_init(&completion->sem, 0, 0); +} + +static inline void env_completion_wait(env_completion *completion) +{ + sem_wait(&completion->sem); +} + +static inline void env_completion_complete(env_completion *completion) +{ + sem_post(&completion->sem); +} + +static inline void env_completion_destroy(env_completion *completion) +{ + sem_destroy(&completion->sem); +} + +#endif /* OCF_ENV_POSIX_COMPLETION */ + +/* ATOMIC */ +#ifdef OCF_ENV_POSIX_ATOMIC + +typedef struct { + volatile uint8_t counter; +} env_atomic8; + +static inline uint8_t env_atomic8_read(const env_atomic8 *a) +{ + return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); +} + +static inline void env_atomic8_set(env_atomic8 *a, uint8_t i) +{ + __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); +} + +static inline uint8_t env_atomic8_cmpxchg(env_atomic8 *a, + uint8_t old, uint8_t new_value) +{ + return __sync_val_compare_and_swap(&a->counter, old, new_value); +} + +static inline void env_atomic8_sub(uint8_t i, env_atomic8 *a) +{ + __sync_sub_and_fetch(&a->counter, i); +} + +static inline void env_atomic8_dec(env_atomic8 *a) +{ + env_atomic8_sub(1, a); +} + +static inline uint8_t env_atomic8_add_unless(env_atomic8 *a, + uint8_t i, uint8_t u) +{ + uint8_t c, old; + + c = env_atomic8_read(a); + for (;;) { + if (unlikely(c == (u))) + break; + old = env_atomic8_cmpxchg((a), c, c + (i)); + if (likely(old == c)) + break; + c = old; + } + return c != (u); +} + +typedef struct { + volatile int counter; +} env_atomic; + +static inline int env_atomic_read(const env_atomic *a) +{ + return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); +} + +static inline void env_atomic_set(env_atomic *a, int i) +{ + __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); +} + +static inline void env_atomic_add(int i, env_atomic *a) +{ + __sync_add_and_fetch(&a->counter, i); +} + +static inline void env_atomic_sub(int i, env_atomic *a) +{ + __sync_sub_and_fetch(&a->counter, i); +} + +static inline void env_atomic_inc(env_atomic *a) +{ + env_atomic_add(1, a); +} + +static inline void env_atomic_dec(env_atomic *a) +{ + env_atomic_sub(1, a); +} + +static inline bool env_atomic_dec_and_test(env_atomic *a) +{ + return __sync_sub_and_fetch(&a->counter, 1) == 0; +} + +static inline int env_atomic_add_return(int i, env_atomic *a) +{ + return __sync_add_and_fetch(&a->counter, i); +} + +static inline int env_atomic_sub_return(int i, env_atomic *a) +{ + return __sync_sub_and_fetch(&a->counter, i); +} + +static inline int env_atomic_inc_return(env_atomic *a) +{ + return env_atomic_add_return(1, a); +} + +static inline int env_atomic_dec_return(env_atomic *a) +{ + return env_atomic_sub_return(1, a); +} + +static inline int env_atomic_cmpxchg(env_atomic *a, int old, int new_value) +{ + return __sync_val_compare_and_swap(&a->counter, old, new_value); +} + +static inline int env_atomic_add_unless(env_atomic *a, int i, int u) +{ + int c, old; + c = env_atomic_read(a); + for (;;) { + if (unlikely(c == (u))) + break; + old = env_atomic_cmpxchg((a), c, c + (i)); + if (likely(old == c)) + break; + c = old; + } + return c != (u); +} + +typedef struct { + volatile long counter; +} env_atomic64; + +static inline long env_atomic64_read(const env_atomic64 *a) +{ + return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); +} + +static inline void env_atomic64_set(env_atomic64 *a, long i) +{ + __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); +} + +static inline void env_atomic64_add(long i, env_atomic64 *a) +{ + __sync_add_and_fetch(&a->counter, i); +} + +static inline void env_atomic64_sub(long i, env_atomic64 *a) +{ + __sync_sub_and_fetch(&a->counter, i); +} + +static inline void env_atomic64_inc(env_atomic64 *a) +{ + env_atomic64_add(1, a); +} + +static inline void env_atomic64_dec(env_atomic64 *a) +{ + env_atomic64_sub(1, a); +} + +static inline long env_atomic64_inc_return(env_atomic64 *a) +{ + return __sync_add_and_fetch(&a->counter, 1); +} + +static inline long env_atomic64_cmpxchg(env_atomic64 *a, long old_v, long new_v) +{ + return __sync_val_compare_and_swap(&a->counter, old_v, new_v); +} + +#endif /* OCF_ENV_POSIX_ATOMIC */ + +/* SPINLOCK */ +#ifdef OCF_ENV_POSIX_SPINLOCK + +typedef struct { + pthread_spinlock_t lock; +} env_spinlock; + +static inline int env_spinlock_init(env_spinlock *l) +{ + return pthread_spin_init(&l->lock, 0); +} + +static inline int env_spinlock_trylock(env_spinlock *l) +{ + return pthread_spin_trylock(&l->lock) ? -OCF_ERR_NO_LOCK : 0; +} + +static inline void env_spinlock_lock(env_spinlock *l) +{ + ENV_BUG_ON(pthread_spin_lock(&l->lock)); +} + +static inline void env_spinlock_unlock(env_spinlock *l) +{ + ENV_BUG_ON(pthread_spin_unlock(&l->lock)); +} + +#define env_spinlock_lock_irqsave(l, flags) \ + (void)flags; \ + env_spinlock_lock(l) + +#define env_spinlock_unlock_irqrestore(l, flags) \ + (void)flags; \ + env_spinlock_unlock(l) + +static inline void env_spinlock_destroy(env_spinlock *l) +{ + ENV_BUG_ON(pthread_spin_destroy(&l->lock)); +} + +#endif /* OCF_ENV_POSIX_SPINLOCK */ + +/* RWLOCK */ +#ifdef OCF_ENV_POSIX_RWLOCK + +typedef struct { + pthread_rwlock_t lock; +} env_rwlock; + +static inline void env_rwlock_init(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_init(&l->lock, NULL)); +} + +static inline void env_rwlock_read_lock(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_rdlock(&l->lock)); +} + +static inline void env_rwlock_read_unlock(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_unlock(&l->lock)); +} + +static inline void env_rwlock_write_lock(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_wrlock(&l->lock)); +} + +static inline void env_rwlock_write_unlock(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_unlock(&l->lock)); +} + +static inline void env_rwlock_destroy(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_destroy(&l->lock)); +} + +#endif /* OCF_ENV_POSIX_RWLOCK */ + +/* BIT */ +#ifdef OCF_ENV_POSIX_BIT + +static inline void env_bit_set(int nr, volatile void *addr) +{ + char *byte = (char *)addr + (nr >> 3); + char mask = 1 << (nr & 7); + + __sync_or_and_fetch(byte, mask); +} + +static inline void env_bit_clear(int nr, volatile void *addr) +{ + char *byte = (char *)addr + (nr >> 3); + char mask = 1 << (nr & 7); + + mask = ~mask; + __sync_and_and_fetch(byte, mask); +} + +static inline bool env_bit_test(int nr, const volatile unsigned long *addr) +{ + const char *byte = (char *)addr + (nr >> 3); + char mask = 1 << (nr & 7); + + __sync_synchronize(); + return !!(*byte & mask); +} + +#endif /* OCF_ENV_POSIX_BIT */ + +/* SCHEDULING */ +#ifdef OCF_ENV_POSIX_SCHEDULING + +static inline int env_in_interrupt(void) +{ + return 0; +} + +#endif /* OCF_ENV_POSIX_SCHEDULING */ + +/* TIME */ +#ifdef OCF_ENV_POSIX_TIME + +#define ENV_SEC_TO_NSEC(_sec) ((_sec) * 1000000000) +#define ENV_NSEC_TO_SEC(_sec) ((_sec) / 1000000000) +#define ENV_NSEC_TO_MSEC(_sec) ((_sec) / 1000000) + +static inline uint64_t env_get_tick_count(void) +{ + struct timespec tv; + + return clock_gettime(CLOCK_REALTIME, &tv) ? + 0 : ENV_SEC_TO_NSEC(tv.tv_sec) + tv.tv_nsec; +} + +static inline uint64_t env_ticks_to_nsecs(uint64_t j) +{ + return j; +} + +static inline uint64_t env_ticks_to_msecs(uint64_t j) +{ + return ENV_NSEC_TO_MSEC(j); +} + +static inline uint64_t env_ticks_to_secs(uint64_t j) +{ + return ENV_NSEC_TO_SEC(j); +} + +static inline uint64_t env_secs_to_ticks(uint64_t j) +{ + return ENV_SEC_TO_NSEC(j); +} + +static inline void env_msleep(uint64_t n) +{ + usleep(n * 1000); +} + +struct env_timeval { + uint64_t sec, usec; +}; + +#endif /* OCF_ENV_POSIX_TIME */ + +/* SORTING */ +#ifdef OCF_ENV_POSIX_SORTING + +static inline void env_sort(void *base, size_t num, size_t size, + int (*cmp_fn)(const void *, const void *), + void (*swap_fn)(void *, void *, int size)) +{ + qsort(base, num, size, cmp_fn); +} + +#endif /* OCF_ENV_POSIX_SORTING */ + +/* CRC */ +#ifdef OCF_ENV_POSIX_CRC + +uint32_t env_crc32(uint32_t crc, uint8_t const *data, size_t len); + +#endif /* OCF_ENV_POSIX_CRC */ + +/* EXECUTION CONTEXT */ +#ifdef OCF_ENV_POSIX_EXECUTION_CONTEXT + +unsigned env_get_execution_context(void); +void env_put_execution_context(unsigned ctx); +unsigned env_get_execution_context_count(void); + +#endif /* OCF_ENV_POSIX_EXECUTION_CONTEXT */ + +#endif /* __OCF_ENV_DEFAULT_H__ */ diff --git a/tests/functional/Makefile b/tests/functional/Makefile index b7ad1e4e5..dcc3af782 100644 --- a/tests/functional/Makefile +++ b/tests/functional/Makefile @@ -1,6 +1,7 @@ # # Copyright(c) 2019-2022 Intel Corporation # Copyright(c) 2025 Huawei Technologies +# Copyright(c) 2026 Unvertical # SPDX-License-Identifier: BSD-3-Clause # @@ -11,6 +12,7 @@ SRCDIR=$(ADAPTERDIR)/ocf/src INCDIR=$(ADAPTERDIR)/ocf/include WRAPDIR=$(ADAPTERDIR)/c/wrappers HELPDIR=$(ADAPTERDIR)/c/helpers +ENVDIR=$(ADAPTERDIR)/c/env CFLAGS=-g -Wall -I$(INCDIR) -I$(SRCDIR)/ocf/env $(OPT_CFLAGS) LDFLAGS=-pthread #-lz @@ -19,9 +21,14 @@ SRC=$(shell find $(SRCDIR) $(WRAPDIR) $(HELPDIR) -name \*.c) OBJS=$(patsubst %.c, %.o, $(SRC)) OCFLIB=$(ADAPTERDIR)/libocf.so -all: | sync config_random +all: | sync env_override config_random $(MAKE) $(OCFLIB) +env_override: sync + @rm -f $(SRCDIR)/ocf/env/ocf_env.h + @cp $(ENVDIR)/ocf_env.h $(SRCDIR)/ocf/env/ocf_env.h + @cp $(ENVDIR)/ocf_env_time.h $(SRCDIR)/ocf/env/ocf_env_time.h + $(OCFLIB): $(OBJS) @echo "Building $@" @$(CC) -coverage -shared -o $@ $(CFLAGS) $^ -fPIC $(LDFLAGS) @@ -51,4 +58,4 @@ distclean: clean @find . -name *.gc* -delete @echo " DISTCLEAN " -.PHONY: all clean sync config_random distclean +.PHONY: all clean sync env_override config_random distclean diff --git a/tests/functional/pyocf/c/env/ocf_env.h b/tests/functional/pyocf/c/env/ocf_env.h new file mode 100644 index 000000000..536667709 --- /dev/null +++ b/tests/functional/pyocf/c/env/ocf_env.h @@ -0,0 +1,37 @@ +/* + * Copyright(c) 2026 Unvertical + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* + * Custom ocf_env.h for pyocf test environment. + * + * Replaces OCF_ENV_POSIX_TIME with a custom implementation that supports + * controllable time offset for testing. + */ + +#ifndef __OCF_ENV_H__ +#define __OCF_ENV_H__ + +#define OCF_ENV_POSIX_DEBUG +#define OCF_ENV_POSIX_STRING +#define OCF_ENV_POSIX_MEMORY +#define OCF_ENV_POSIX_MUTEX +#define OCF_ENV_POSIX_RMUTEX +#define OCF_ENV_POSIX_RWSEM +#define OCF_ENV_POSIX_COMPLETION +#define OCF_ENV_POSIX_ATOMIC +#define OCF_ENV_POSIX_SPINLOCK +#define OCF_ENV_POSIX_RWLOCK +#define OCF_ENV_POSIX_BIT +#define OCF_ENV_POSIX_SCHEDULING +/* OCF_ENV_POSIX_TIME intentionally not defined - using custom impl */ +#define OCF_ENV_POSIX_SORTING +#define OCF_ENV_POSIX_CRC +#define OCF_ENV_POSIX_EXECUTION_CONTEXT + +#include "ocf_env_default.h" + +#include "ocf_env_time.h" + +#endif /* __OCF_ENV_H__ */ diff --git a/tests/functional/pyocf/c/env/ocf_env_time.h b/tests/functional/pyocf/c/env/ocf_env_time.h new file mode 100644 index 000000000..8711252ac --- /dev/null +++ b/tests/functional/pyocf/c/env/ocf_env_time.h @@ -0,0 +1,66 @@ +/* + * Copyright(c) 2026 Unvertical + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* + * Custom TIME section for pyocf test environment. + * + * Replaces the default posix env_get_tick_count with a fully test-controlled + * counter. The "current time" is solely the value of ocf_env_tick_count_offset, + * which tests advance explicitly. This makes time deterministic for cleaner + * tests and avoids real-time drift between OCF time queries within a single + * test. + * + * All other time functions (conversions, sleep) remain identical to the default + * posix implementation. + */ + +#ifndef __OCF_ENV_TIME_H__ +#define __OCF_ENV_TIME_H__ + +#include +#include +#include + +#define ENV_SEC_TO_NSEC(_sec) ((_sec) * 1000000000) +#define ENV_NSEC_TO_SEC(_sec) ((_sec) / 1000000000) +#define ENV_NSEC_TO_MSEC(_sec) ((_sec) / 1000000) + +extern uint64_t ocf_env_tick_count_offset; + +static inline uint64_t env_get_tick_count(void) +{ + return ocf_env_tick_count_offset; +} + +static inline uint64_t env_ticks_to_nsecs(uint64_t j) +{ + return j; +} + +static inline uint64_t env_ticks_to_msecs(uint64_t j) +{ + return ENV_NSEC_TO_MSEC(j); +} + +static inline uint64_t env_ticks_to_secs(uint64_t j) +{ + return ENV_NSEC_TO_SEC(j); +} + +static inline uint64_t env_secs_to_ticks(uint64_t j) +{ + return ENV_SEC_TO_NSEC(j); +} + +static inline void env_msleep(uint64_t n) +{ + usleep(n * 1000); +} + +struct env_timeval { + uint64_t sec, usec; +}; + +#endif /* __OCF_ENV_TIME_H__ */ diff --git a/tests/functional/pyocf/c/helpers/ocf_helpers.c b/tests/functional/pyocf/c/helpers/ocf_helpers.c index dd149cc4c..2b578ec2b 100644 --- a/tests/functional/pyocf/c/helpers/ocf_helpers.c +++ b/tests/functional/pyocf/c/helpers/ocf_helpers.c @@ -7,6 +7,8 @@ #include "ocf/ocf.h" #include "../src/ocf/ocf_def_priv.h" +uint64_t ocf_env_tick_count_offset = 0; + bool ocf_is_block_size_4k(void) { #ifdef OCF_BLOCK_SIZE_4K diff --git a/tests/functional/pyocf/time.py b/tests/functional/pyocf/time.py new file mode 100644 index 000000000..4ca3c61e0 --- /dev/null +++ b/tests/functional/pyocf/time.py @@ -0,0 +1,28 @@ +# +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# + +from ctypes import c_uint64 + +from .ocf import OcfLib + +SEC_IN_NS = 1_000_000_000 + + +def advance_time(secs): + lib = OcfLib.getInstance() + offset = c_uint64.in_dll(lib, "ocf_env_tick_count_offset") + offset.value += secs * SEC_IN_NS + + +def advance_time_ms(ms): + lib = OcfLib.getInstance() + offset = c_uint64.in_dll(lib, "ocf_env_tick_count_offset") + offset.value += ms * 1_000_000 + + +def reset_time(): + lib = OcfLib.getInstance() + offset = c_uint64.in_dll(lib, "ocf_env_tick_count_offset") + offset.value = 0 diff --git a/tests/functional/pyocf/types/cleaner.py b/tests/functional/pyocf/types/cleaner.py index 0315b175b..0af66cff5 100644 --- a/tests/functional/pyocf/types/cleaner.py +++ b/tests/functional/pyocf/types/cleaner.py @@ -1,10 +1,13 @@ # # Copyright(c) 2019-2021 Intel Corporation +# Copyright(c) 2026 Unvertical # SPDX-License-Identifier: BSD-3-Clause # -from ctypes import c_void_p, CFUNCTYPE, Structure, c_int +from ctypes import c_void_p, c_uint32, c_int, CFUNCTYPE, Structure +from threading import Thread, Timer, Event from .shared import SharedOcfObject +from ..ocf import OcfLib class CleanerOps(Structure): @@ -15,9 +18,25 @@ class CleanerOps(Structure): _fields_ = [("init", INIT), ("kick", KICK), ("stop", STOP)] +CLEANER_END = CFUNCTYPE(None, c_void_p, c_uint32) + + +class _CleanerState: + def __init__(self): + self.kick_event = Event() + self.stop_event = Event() + self.thread = None + self.timer = None + self.queue = None + self.last_interval = 0 + + class Cleaner(SharedOcfObject): _instances_ = {} _fields_ = [("cleaner", c_void_p)] + _cleaners = {} + _end_handler = None + _kick_handler = None def __init__(self): self._as_parameter_ = self.cleaner @@ -27,17 +46,133 @@ def __init__(self): def get_ops(cls): return CleanerOps(init=cls._init, kick=cls._kick, stop=cls._stop) + @classmethod + def set_end_handler(cls, handler): + """Override the default end handler. + + The handler receives (cleaner, interval) and is responsible for + scheduling the next cleaner iteration. Set to None to restore + the default behaviour (wait the interval before re-kicking). + """ + cls._end_handler = handler + + @classmethod + def set_kick_handler(cls, handler): + """Override the default kick handler. + + The handler receives (cleaner) and decides whether to schedule a + cleaner iteration. Set to None to restore the default behaviour + (kick the cleaner thread to run an iteration). + """ + cls._kick_handler = handler + + @staticmethod + def _resolve_queue(cleaner): + from pyocf.types.ctx import OcfCtx + + cache_handle = lib.ocf_cleaner_get_cache(cleaner) + + ctx = OcfCtx.get_default() + if ctx is None: + return None + + for c in ctx.caches: + if c.cache_handle.value == cache_handle and c.io_queues: + return c.io_queues[0] + + return None + + @staticmethod + def _cleaner_thread(cleaner): + state = Cleaner._cleaners.get(cleaner) + if not state: + return + + while not state.stop_event.is_set(): + state.kick_event.wait() + if state.stop_event.is_set(): + break + state.kick_event.clear() + if state.queue: + lib.ocf_cleaner_run(cleaner, state.queue.handle) + + @staticmethod + def _default_end(cleaner, interval): + state = Cleaner._cleaners.get(cleaner) + if state is None or state.stop_event.is_set(): + return + state.last_interval = interval + if interval > 0: + state.timer = Timer( + interval / 1000.0, state.kick_event.set + ) + state.timer.daemon = True + state.timer.start() + else: + state.kick_event.set() + @staticmethod @CleanerOps.INIT def _init(cleaner): + lib.ocf_cleaner_set_cmpl(cleaner, Cleaner._end) + + state = _CleanerState() + state.queue = Cleaner._resolve_queue(cleaner) + Cleaner._cleaners[cleaner] = state + + state.thread = Thread( + target=Cleaner._cleaner_thread, + args=(cleaner,), + daemon=True, + name="cleaner", + ) + state.thread.start() return 0 + @staticmethod + def _default_kick(cleaner): + state = Cleaner._cleaners.get(cleaner) + if state is None: + return + if state.queue is None: + state.queue = Cleaner._resolve_queue(cleaner) + state.kick_event.set() + @staticmethod @CleanerOps.KICK def _kick(cleaner): - pass + handler = Cleaner._kick_handler + if handler is not None: + handler(cleaner) + else: + Cleaner._default_kick(cleaner) + + @staticmethod + @CLEANER_END + def _end(cleaner, interval): + handler = Cleaner._end_handler + if handler is not None: + handler(cleaner, interval) + else: + Cleaner._default_end(cleaner, interval) @staticmethod @CleanerOps.STOP def _stop(cleaner): - pass + state = Cleaner._cleaners.pop(cleaner, None) + if state is None: + return + state.stop_event.set() + if state.timer: + state.timer.cancel() + state.kick_event.set() + if state.thread and state.thread.is_alive(): + state.thread.join(timeout=5) + + +lib = OcfLib.getInstance() +lib.ocf_cleaner_set_cmpl.argtypes = [c_void_p, CLEANER_END] +lib.ocf_cleaner_get_cache.argtypes = [c_void_p] +lib.ocf_cleaner_get_cache.restype = c_void_p +lib.ocf_cleaner_run.argtypes = [c_void_p, c_void_p] +lib.ocf_kick_cleaner.argtypes = [c_void_p] diff --git a/tests/functional/pyocf/utils.py b/tests/functional/pyocf/utils.py index 01b5dba64..1799120fa 100644 --- a/tests/functional/pyocf/utils.py +++ b/tests/functional/pyocf/utils.py @@ -200,9 +200,13 @@ def __mul__(self, other): return Size(self.bytes * int(other)) def __truediv__(self, other): + if isinstance(other, Size): + return self.bytes / other.bytes return Size(self.bytes / int(other)) def __floordiv__(self, other): + if isinstance(other, Size): + return self.bytes // other.bytes return Size(self.bytes // int(other)) def __rmul__(self, other): diff --git a/tests/functional/tests/cleaning/__init__.py b/tests/functional/tests/cleaning/__init__.py new file mode 100644 index 000000000..ccf889cf8 --- /dev/null +++ b/tests/functional/tests/cleaning/__init__.py @@ -0,0 +1,4 @@ +# +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# diff --git a/tests/functional/tests/cleaning/conftest.py b/tests/functional/tests/cleaning/conftest.py new file mode 100644 index 000000000..0470e6463 --- /dev/null +++ b/tests/functional/tests/cleaning/conftest.py @@ -0,0 +1,79 @@ +# +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# + +import pytest +from ctypes import c_uint64 +from threading import Event + +from pyocf.types.cleaner import Cleaner +from pyocf.ocf import OcfLib +from pyocf.time import reset_time + + +class ManualCleaner: + """Controls cleaner iterations one at a time from the test. + + Suppresses background cleaner kicks (e.g. those issued by ALRU/ACP + when dirty data is created) so the cleaner only runs when run() is + called. Each run() executes a single ocf_cleaner_run iteration and + waits for its completion callback. + """ + + def __init__(self): + self._done = Event() + self.last_interval = None + + def _end_handler(self, cleaner, interval): + state = Cleaner._cleaners.get(cleaner) + if state is not None: + state.last_interval = interval + self.last_interval = interval + self._done.set() + + def run(self, cache): + """Kick one cleaner iteration and wait for it to complete.""" + self._done.clear() + self.last_interval = None + lib = OcfLib.getInstance() + cleaner_ptr = None + for ptr in Cleaner._cleaners: + if lib.ocf_cleaner_get_cache(ptr) == cache.cache_handle.value: + cleaner_ptr = ptr + break + assert cleaner_ptr is not None, "No cleaner registered for cache" + Cleaner._default_kick(cleaner_ptr) + assert self._done.wait(timeout=5), "Cleaner iteration timed out" + cache.settle() + + def run_until_idle(self, cache): + """Run iterations until the cleaner reports it has nothing to do. + + The cleaner returns interval == 0 from its end callback while it + still has work to do (asking to be kicked again immediately) and + a non-zero interval (the configured wake_up_time, in ms) once it + is finished. This loop keeps kicking the cleaner until that + non-zero interval is reported. + """ + for _ in range(10000): + self.run(cache) + if self.last_interval > 0: + return + raise AssertionError("Cleaner did not become idle after 10000 iterations") + + def install(self): + Cleaner.set_kick_handler(lambda cleaner: None) + Cleaner.set_end_handler(self._end_handler) + + def uninstall(self): + Cleaner.set_end_handler(None) + Cleaner.set_kick_handler(None) + + +@pytest.fixture +def manual_cleaner(): + mc = ManualCleaner() + mc.install() + yield mc + mc.uninstall() diff --git a/tests/functional/tests/cleaning/test_acp.py b/tests/functional/tests/cleaning/test_acp.py new file mode 100644 index 000000000..6a15400a2 --- /dev/null +++ b/tests/functional/tests/cleaning/test_acp.py @@ -0,0 +1,137 @@ +# +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# + +import pytest + +from pyocf.types.cache import ( + Cache, + CacheMode, + CleaningPolicy, + AcpParams, +) +from pyocf.types.core import Core +from pyocf.types.volume import RamVolume +from pyocf.types.volume_core import CoreVolume +from pyocf.types.shared import CacheLineSize +from pyocf.utils import Size +from pyocf.rio import Rio, ReadWrite + + +STATS_BLOCK_SIZE = Size.from_KiB(4) + + +def dirty_blocks(stats): + return stats["usage"]["dirty"]["value"] + + +def cleaner_req(stats): + return stats["req"]["cleaner"]["value"] + + +def cleaner_cache_rd(stats): + return stats["block"]["cleaner_cache_rd"]["value"] + + +def cleaner_core_wr(stats): + return stats["block"]["cleaner_core_wr"]["value"] + + +# -- cache setup helpers -- + +def setup_cache(pyocf_ctx, cls=CacheLineSize.DEFAULT): + cache_device = RamVolume(Size.from_MiB(50)) + core_device = RamVolume(Size.from_MiB(100)) + + cache = Cache.start_on_device( + cache_device, cache_mode=CacheMode.WB, + cache_line_size=cls, metadata_volatile=True, + ) + core = Core.using_device(core_device) + cache.add_core(core) + + cache.set_cleaning_policy(CleaningPolicy.NOP) + + vol = CoreVolume(core) + queue = cache.get_default_queue() + + return cache, core, vol, queue + + +def fill_dirty(vol, queue, offset, size): + Rio().target(vol).bs(Size.from_KiB(4)).size(size).offset(offset) \ + .readwrite(ReadWrite.WRITE).run([queue]) + + +# -- tests -- + +@pytest.mark.parametrize("wake_up", [0, 1, 5, 20, 100]) +def test_acp_wake_up_time(pyocf_ctx, manual_cleaner, wake_up): + """Verify wake_up_time is propagated as the interval to the cleaner + completion callback after a flush.""" + cache, core, vol, queue = setup_cache(pyocf_ctx) + + io_size = Size.from_MiB(1) + fill_dirty(vol, queue, Size(0), io_size) + + cache.set_cleaning_policy_param( + CleaningPolicy.ACP, AcpParams.WAKE_UP_TIME, wake_up + ) + cache.set_cleaning_policy(CleaningPolicy.ACP) + + manual_cleaner.run(cache) + + assert manual_cleaner.last_interval == wake_up + + +@pytest.mark.parametrize("cls", CacheLineSize) +@pytest.mark.parametrize("flush_max", [1, 10, 100]) +def test_acp_flush_max_buffers(pyocf_ctx, manual_cleaner, cls, flush_max): + """Verify each cleaner iteration flushes at most flush_max_buffers + cache lines.""" + cache, core, vol, queue = setup_cache(pyocf_ctx, cls=cls) + + io_size = Size.from_MiB(10) + fill_dirty(vol, queue, Size(0), io_size) + + total_clines = io_size // Size(cls) + blocks_per_cline = Size(cls) // STATS_BLOCK_SIZE + + cache.set_cleaning_policy_param( + CleaningPolicy.ACP, AcpParams.WAKE_UP_TIME, 0 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ACP, AcpParams.FLUSH_MAX_BUFFERS, flush_max + ) + cache.set_cleaning_policy(CleaningPolicy.ACP) + + cleaned_clines = 0 + remaining = total_clines + + while remaining > 0: + stats = cache.get_stats() + prev_req = cleaner_req(stats) + prev_rd = cleaner_cache_rd(stats) + prev_wr = cleaner_core_wr(stats) + + manual_cleaner.run(cache) + + stats = cache.get_stats() + batch = cleaner_req(stats) - prev_req + rd_batch = cleaner_cache_rd(stats) - prev_rd + wr_batch = cleaner_core_wr(stats) - prev_wr + + expected = min(flush_max, remaining) + assert batch == expected, \ + f"Expected {expected} cache lines flushed, got {batch}" + assert rd_batch == expected * blocks_per_cline, \ + f"Expected {expected * blocks_per_cline} cache read blocks, got {rd_batch}" + assert wr_batch == expected * blocks_per_cline, \ + f"Expected {expected * blocks_per_cline} core write blocks, got {wr_batch}" + + remaining -= batch + cleaned_clines += batch + + assert dirty_blocks(cache.get_stats()) == 0 + assert cleaned_clines == total_clines diff --git a/tests/functional/tests/cleaning/test_alru.py b/tests/functional/tests/cleaning/test_alru.py new file mode 100644 index 000000000..5bad93bac --- /dev/null +++ b/tests/functional/tests/cleaning/test_alru.py @@ -0,0 +1,259 @@ +# +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# + +import pytest + +from pyocf.time import advance_time, advance_time_ms, reset_time +from pyocf.types.cache import ( + Cache, + CacheMode, + CleaningPolicy, + AlruParams, +) +from pyocf.types.core import Core +from pyocf.types.volume import RamVolume +from pyocf.types.volume_core import CoreVolume +from pyocf.types.shared import CacheLineSize +from pyocf.utils import Size +from pyocf.rio import Rio, ReadWrite + + +STATS_BLOCK_SIZE = Size.from_KiB(4) + + +def dirty_blocks(stats): + return stats["usage"]["dirty"]["value"] + + +def cleaner_req(stats): + return stats["req"]["cleaner"]["value"] + + +def cleaner_cache_rd(stats): + return stats["block"]["cleaner_cache_rd"]["value"] + + +def cleaner_core_wr(stats): + return stats["block"]["cleaner_core_wr"]["value"] + + +# -- cache setup helpers -- + +def setup_cache(pyocf_ctx, cls=CacheLineSize.DEFAULT): + cache_device = RamVolume(Size.from_MiB(50)) + core_device = RamVolume(Size.from_MiB(100)) + + cache = Cache.start_on_device( + cache_device, cache_mode=CacheMode.WB, + cache_line_size=cls, metadata_volatile=True, + ) + core = Core.using_device(core_device) + cache.add_core(core) + + cache.set_cleaning_policy(CleaningPolicy.NOP) + + vol = CoreVolume(core) + queue = cache.get_default_queue() + + return cache, core, vol, queue + + +def fill_dirty(vol, queue, offset, size): + Rio().target(vol).bs(Size.from_KiB(4)).size(size).offset(offset) \ + .readwrite(ReadWrite.WRITE).run([queue]) + + +# -- tests -- + +@pytest.mark.parametrize("wake_up", [0, 1, 5, 20, 100]) +def test_alru_wake_up_time(pyocf_ctx, manual_cleaner, wake_up): + """Verify wake_up_time is propagated as the interval to the cleaner + completion callback when no flush was performed.""" + cache, core, vol, queue = setup_cache(pyocf_ctx) + + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.WAKE_UP_TIME, wake_up + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.ACTIVITY_THRESHOLD, 0 + ) + cache.set_cleaning_policy(CleaningPolicy.ALRU) + + # No dirty data — cleaner has nothing to do. + # advance past clean_later check + advance_time(wake_up + 1) + + manual_cleaner.run(cache) + + assert manual_cleaner.last_interval == wake_up * 1000 + + +@pytest.mark.parametrize("stale_time", [3, 5, 10]) +def test_alru_staleness_time(pyocf_ctx, manual_cleaner, stale_time): + """Verify only data older than stale_buffer_time is cleaned.""" + cache, core, vol, queue = setup_cache(pyocf_ctx) + + # Enable ALRU before writes so each cache line gets its own per-write + # timestamp via add_alru_head. (Switching from NOP would reset all + # timestamps to a single "now" value for the existing dirty clines.) + # wake_up_time=1: the cleaner's clean_later check enforces at least + # 1 sec between back-to-back cleaner attempts. activity_threshold=200 + # is small enough never to interfere. + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.WAKE_UP_TIME, 1 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.STALE_BUFFER_TIME, stale_time + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.ACTIVITY_THRESHOLD, 200 + ) + cache.set_cleaning_policy(CleaningPolicy.ALRU) + + half = Size.from_MiB(5) + half_blocks = half // STATS_BLOCK_SIZE + + # Fill first half (timestamp == 0 sec) + fill_dirty(vol, queue, Size(0), half) + + assert dirty_blocks(cache.get_stats()) == half_blocks + + # Advance 2.5 sec — crosses the activity threshold and moves the + # clock two whole seconds forward so the second half gets a + # distinct second-precision timestamp. + advance_time_ms(2500) + + # Fill second half (timestamp == 2 sec) + fill_dirty(vol, queue, half, half) + + assert dirty_blocks(cache.get_stats()) == half_blocks * 2 + + # Advance to exactly stale_time seconds from T0. This is the + # earliest point where compute_timestamp doesn't underflow (so + # the cleaner makes a meaningful eligibility decision), while + # keeping compute_timestamp == 0 — neither half eligible yet. + advance_time_ms(stale_time * 1000 - 2500) + + # Cleaner runs but flushes nothing — both halves still inside + # the staleness window. + manual_cleaner.run_until_idle(cache) + assert dirty_blocks(cache.get_stats()) == half_blocks * 2, \ + "No data should be flushed before staleness time is reached" + + # Advance another 2.5 sec → compute_timestamp == 2. First half + # (ts=0) becomes eligible; second half (ts=2) does not. The + # 2 sec gap also satisfies the wake_up_time clean_later check + # after the previous cleaner attempt. + advance_time_ms(2500) + + manual_cleaner.run_until_idle(cache) + assert dirty_blocks(cache.get_stats()) == half_blocks + + # Advance another 2 sec so compute_timestamp == 4 and the second + # half also becomes stale. + advance_time(2) + + manual_cleaner.run_until_idle(cache) + assert dirty_blocks(cache.get_stats()) == 0 + + +@pytest.mark.parametrize("cls", CacheLineSize) +@pytest.mark.parametrize("flush_max", [1, 10, 100]) +def test_alru_flush_max_buffers(pyocf_ctx, manual_cleaner, cls, flush_max): + """Verify each cleaner iteration flushes at most flush_max_buffers + cache lines.""" + cache, core, vol, queue = setup_cache(pyocf_ctx, cls=cls) + + io_size = Size.from_MiB(10) + fill_dirty(vol, queue, Size(0), io_size) + + total_clines = io_size // Size(cls) + blocks_per_cline = Size(cls) // STATS_BLOCK_SIZE + + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.WAKE_UP_TIME, 0 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.STALE_BUFFER_TIME, 1 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.ACTIVITY_THRESHOLD, 0 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.FLUSH_MAX_BUFFERS, flush_max + ) + cache.set_cleaning_policy(CleaningPolicy.ALRU) + + advance_time(2) + + cleaned_clines = 0 + remaining = total_clines + + while remaining > 0: + stats = cache.get_stats() + prev_req = cleaner_req(stats) + prev_rd = cleaner_cache_rd(stats) + prev_wr = cleaner_core_wr(stats) + + manual_cleaner.run(cache) + + stats = cache.get_stats() + batch = cleaner_req(stats) - prev_req + rd_batch = cleaner_cache_rd(stats) - prev_rd + wr_batch = cleaner_core_wr(stats) - prev_wr + + expected = min(flush_max, remaining) + assert batch == expected, \ + f"Expected {expected} cache lines flushed, got {batch}" + assert rd_batch == expected * blocks_per_cline, \ + f"Expected {expected * blocks_per_cline} cache read blocks, got {rd_batch}" + assert wr_batch == expected * blocks_per_cline, \ + f"Expected {expected * blocks_per_cline} core write blocks, got {wr_batch}" + + remaining -= batch + cleaned_clines += batch + + assert dirty_blocks(cache.get_stats()) == 0 + assert cleaned_clines == total_clines + + +@pytest.mark.parametrize("activity_threshold", [2000, 5000, 10000]) +def test_alru_activity_threshold(pyocf_ctx, manual_cleaner, activity_threshold): + """Verify cleaner does not run while I/O activity is recent, and starts + cleaning once the activity threshold is exceeded.""" + cache, core, vol, queue = setup_cache(pyocf_ctx) + + io_size = Size.from_MiB(10) + fill_dirty(vol, queue, Size(0), io_size) + + total_blocks = io_size // STATS_BLOCK_SIZE + + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.WAKE_UP_TIME, 1 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.STALE_BUFFER_TIME, 1 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.ACTIVITY_THRESHOLD, activity_threshold + ) + cache.set_cleaning_policy(CleaningPolicy.ALRU) + + # Advance past staleness (1 sec) but NOT past activity threshold. + # All test values are >= 2000 ms, so 1500 ms is always inside the + # activity window while still past the staleness boundary. + advance_time_ms(1500) + + manual_cleaner.run(cache) + + assert dirty_blocks(cache.get_stats()) == total_blocks, \ + "Cleaner should not run while within activity threshold" + + # Now advance past the activity threshold + advance_time_ms(activity_threshold) + + manual_cleaner.run_until_idle(cache) + + assert dirty_blocks(cache.get_stats()) == 0 diff --git a/tests/functional/tests/conftest.py b/tests/functional/tests/conftest.py index 94920a3ee..6b39d68d4 100644 --- a/tests/functional/tests/conftest.py +++ b/tests/functional/tests/conftest.py @@ -19,6 +19,7 @@ from pyocf.types.ctx import OcfCtx from pyocf.helpers import get_composite_volume_type_id from pyocf.types.volume import Volume +from pyocf.time import reset_time import warnings default_registered_volumes = [RamVolume, ErrorDevice, CacheVolume, CoreVolume, ReplicatedVolume] @@ -44,6 +45,7 @@ def pyocf_ctx(request): c.register_internal_volume_type_id(CVolume, get_composite_volume_type_id()) yield c c.exit() + reset_time() gc.collect() if getattr(request.node, "test_failed", False): return