diff options
author | Suren A. Chilingaryan <csa@suren.me> | 2015-08-05 00:52:55 +0200 |
---|---|---|
committer | Suren A. Chilingaryan <csa@suren.me> | 2015-08-05 00:52:55 +0200 |
commit | b05d6147eb4ccc7527a806506c2154c7795487f6 (patch) | |
tree | 28bbafa26a576b1d7896b78de51ed3e5f724e535 /pcilib | |
parent | 61068e3ba4863cf41b2399ea3057b3bb1c4a9dcf (diff) | |
parent | edd5ccf24c146915ee475bd223e2ad695520a241 (diff) | |
download | pcitool-b05d6147eb4ccc7527a806506c2154c7795487f6.tar.gz pcitool-b05d6147eb4ccc7527a806506c2154c7795487f6.tar.bz2 pcitool-b05d6147eb4ccc7527a806506c2154c7795487f6.tar.xz pcitool-b05d6147eb4ccc7527a806506c2154c7795487f6.zip |
Integrate locking subsystem from Nicolas Zilio
Diffstat (limited to 'pcilib')
-rw-r--r-- | pcilib/CMakeLists.txt | 4 | ||||
-rw-r--r-- | pcilib/config.h.in | 1 | ||||
-rw-r--r-- | pcilib/kmem.c | 12 | ||||
-rw-r--r-- | pcilib/kmem.h | 6 | ||||
-rw-r--r-- | pcilib/lock.c | 201 | ||||
-rw-r--r-- | pcilib/lock.h | 99 | ||||
-rw-r--r-- | pcilib/locking.c | 314 | ||||
-rw-r--r-- | pcilib/locking.h | 48 | ||||
-rw-r--r-- | pcilib/pci.c | 22 | ||||
-rw-r--r-- | pcilib/pci.h | 3 |
10 files changed, 703 insertions, 7 deletions
diff --git a/pcilib/CMakeLists.txt b/pcilib/CMakeLists.txt index c9bf0fb..0bf0e66 100644 --- a/pcilib/CMakeLists.txt +++ b/pcilib/CMakeLists.txt @@ -3,8 +3,8 @@ include_directories( ${CMAKE_SOURCE_DIR}/pcilib ) -set(HEADERS pcilib.h pci.h export.h bar.h fifo.h model.h bank.h register.h kmem.h irq.h dma.h event.h plugin.h tools.h error.h debug.h env.h version.h config.h) -add_library(pcilib SHARED pci.c export.c bar.c fifo.c model.c bank.c register.c kmem.c irq.c dma.c event.c plugin.c tools.c error.c debug.c env.c) +set(HEADERS pcilib.h pci.h export.h bar.h fifo.h model.h bank.h register.h kmem.h irq.h locking.h lock.h dma.h event.h plugin.h tools.h error.h debug.h env.h version.h config.h) +add_library(pcilib SHARED pci.c export.c bar.c fifo.c model.c bank.c register.c kmem.c irq.c locking.c lock.c dma.c event.c plugin.c tools.c error.c debug.c env.c ) target_link_libraries(pcilib dma protocols ${CMAKE_THREAD_LIBS_INIT} ${UFODECODE_LIBRARIES} ${CMAKE_DL_LIBS}) add_dependencies(pcilib dma protocols) diff --git a/pcilib/config.h.in b/pcilib/config.h.in index e588612..bdd9ec3 100644 --- a/pcilib/config.h.in +++ b/pcilib/config.h.in @@ -2,3 +2,4 @@ #cmakedefine PCILIB_DATA_DIR "${PCILIB_DATA_DIR}" #cmakedefine PCILIB_MODEL_DIR "${PCILIB_MODEL_DIR}" #cmakedefine PCILIB_DEBUG_DIR "${PCILIB_DEBUG_DIR}" +#cmakedefine HAVE_STDATOMIC_H @HAVE_STDATOMIC_H@ diff --git a/pcilib/kmem.c b/pcilib/kmem.c index 8560eae..2ff674f 100644 --- a/pcilib/kmem.c +++ b/pcilib/kmem.c @@ -79,9 +79,16 @@ pcilib_kmem_handle_t *pcilib_alloc_kernel_memory(pcilib_t *ctx, pcilib_kmem_type } memset(kbuf, 0, sizeof(pcilib_kmem_list_t) + nmemb * sizeof(pcilib_kmem_addr_t)); - + + err = pcilib_lock(ctx->locks.mmap); + if (!err) { + pcilib_error("Error acquiring mmap lock"); + return NULL; + } + ret = ioctl( ctx->handle, PCIDRIVER_IOC_MMAP_MODE, PCIDRIVER_MMAP_KMEM ); if (ret) { + pcilib_unlock(ctx->locks.mmap); pcilib_error("PCIDRIVER_IOC_MMAP_MODE ioctl have failed"); return NULL; } @@ -169,6 +176,9 @@ pcilib_kmem_handle_t *pcilib_alloc_kernel_memory(pcilib_t *ctx, pcilib_kmem_type kbuf->buf.blocks[i].mmap_offset = kh.pa & ctx->page_mask; } + pcilib_unlock(ctx->locks.mmap); + + //This is possible in the case of error (nothing is allocated yet) or if buffers are not reused if (persistent < 0) persistent = 0; if (hardware < 0) hardware = 0; diff --git a/pcilib/kmem.h b/pcilib/kmem.h index 8299379..9cd1971 100644 --- a/pcilib/kmem.h +++ b/pcilib/kmem.h @@ -1,6 +1,8 @@ #ifndef _PCILIB_KMEM_H #define _PCILIB_KMEM_H +#include <stdint.h> + typedef struct pcilib_s pcilib_t; typedef struct pcilib_kmem_list_s pcilib_kmem_list_t; @@ -14,7 +16,8 @@ typedef enum { #define PCILIB_KMEM_TYPE_MASK 0xFFFF0000 #define PCILIB_KMEM_USE(type, subtype) ((pcilib_kmem_use_t)(((type) << 16)|(subtype))) - +#define PCILIB_KMEM_USE_TYPE(use) (use >> 16) +#define PCILIB_KMEM_USE_SUBTYPE(use) (use & 0xFFFF) typedef enum { PCILIB_KMEM_TYPE_CONSISTENT = 0x00000, @@ -31,6 +34,7 @@ typedef enum { PCILIB_KMEM_USE_DMA_RING = 1, PCILIB_KMEM_USE_DMA_PAGES = 2, PCILIB_KMEM_USE_SOFTWARE_REGISTERS = 3, + PCILIB_KMEM_USE_LOCKS = 4, PCILIB_KMEM_USE_USER = 0x10 } pcilib_kmem_use_t; diff --git a/pcilib/lock.c b/pcilib/lock.c new file mode 100644 index 0000000..f1cbc56 --- /dev/null +++ b/pcilib/lock.c @@ -0,0 +1,201 @@ +#define _GNU_SOURCE +#define _XOPEN_SOURCE 600 + +#include "config.h" + +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <assert.h> +#include <pthread.h> +#include <stdint.h> + +#ifdef HAVE_STDATOMIC_H +# include <stdatomic.h> +#endif /* HAVE_STDATOMIC_H */ + +#include "error.h" +#include "lock.h" +#include "pci.h" + + +struct pcilib_lock_s { + pthread_mutex_t mutex; + pcilib_lock_flags_t flags; +#ifdef HAVE_STDATOMIC_H + volatile atomic_uint refs; +#else /* HAVE_STDATOMIC_H */ + volatile uint32_t refs; +#endif /* HAVE_STDATOMIC_H */ + char name[]; +}; + + +/** + * this function initialize a new semaphore in the kernel if it's not already initialized given the key that permits to differentiate semaphores, and then return the integer that points to the semaphore that have been initialized or to a previously already initialized semaphore + */ +int pcilib_init_lock(pcilib_lock_t *lock, pcilib_lock_flags_t flags, const char *lock_id) { + int err; + pthread_mutexattr_t attr; + + assert(lock); + assert(lock_id); + + memset(lock, 0, PCILIB_LOCK_SIZE); + + if (strlen(lock_id) >= (PCILIB_LOCK_SIZE - offsetof(struct pcilib_lock_s, name))) { + pcilib_error("The supplied lock id (%s) is too long...", lock_id); + return PCILIB_ERROR_INVALID_ARGUMENT; + } + + if ((err = pthread_mutexattr_init(&attr))!=0) { + pcilib_error("Can't initialize mutex attribute, errno %i", errno); + return PCILIB_ERROR_FAILED; + } + + if ((err = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED))!=0) { + pcilib_error("Can't configure a shared mutex attribute, errno %i", errno); + return PCILIB_ERROR_FAILED; + } + + if ((flags&PCILIB_LOCK_FLAG_PERSISTENT)==0) { + if ((err = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST))!=0) { + pcilib_error("Can't configure a robust mutex attribute, errno: %i", errno); + return PCILIB_ERROR_FAILED; + } + } + + if ((err = pthread_mutex_init(&lock->mutex, &attr))!=0) { + pcilib_error("Can't create mutex, errno : %i",errno); + return PCILIB_ERROR_FAILED; + } + + strcpy(lock->name, lock_id); + lock->refs = 1; + lock->flags = flags; + + return 0; +} + + +/* + * we uninitialize a mutex and set its name to 0 pointed by lock_ctx with this function. setting name to is the real destroying operation, but we need to unitialize the lock to initialize it again after + */ +void pcilib_free_lock(pcilib_lock_t *lock) { + int err; + + assert(lock); + +// if (lock->refs) +// pcilib_error("Forbidding to destroy the referenced mutex..."); + + if ((err = pthread_mutex_destroy(&lock->mutex))==-1) + pcilib_warning("Can't destroy POSIX mutex, errno %i",errno); +} + + +void pcilib_lock_ref(pcilib_lock_t *lock) { + assert(lock); + +#ifdef HAVE_STDATOMIC_H + atomic_fetch_add_explicit(&lock->refs, 1, memory_order_relaxed); +#else /* HAVE_STDATOMIC_H */ + lock->refs++; +#endif /* HAVE_STDATOMIC_H */ +} + +void pcilib_lock_unref(pcilib_lock_t *lock) { + assert(lock); + + if (!lock->refs) { + pcilib_warning("Lock is not referenced"); + return; + } + +#ifdef HAVE_STDATOMIC_H + atomic_fetch_sub_explicit(&lock->refs, 1, memory_order_relaxed); +#else /* HAVE_STDATOMIC_H */ + lock->refs--; +#endif /* HAVE_STDATOMIC_H */ +} + +size_t pcilib_lock_get_refs(pcilib_lock_t *lock) { + return lock->refs; +} + +pcilib_lock_flags_t pcilib_lock_get_flags(pcilib_lock_t *lock) { + return lock->flags; +} + +const char *pcilib_lock_get_name(pcilib_lock_t *lock) { + assert(lock); + + if (lock->name[0]) return lock->name; + return NULL; +} + +/* + * this function will take the lock for the semaphore pointed by semId + */ +int pcilib_lock_custom(pcilib_lock_t *lock, pcilib_lock_flags_t flags, pcilib_timeout_t timeout) { + int err; + + if (!lock) return 0; + + struct timespec tm; + + switch (timeout) { + case PCILIB_TIMEOUT_INFINITE: + err = pthread_mutex_lock(&lock->mutex); + break; + case PCILIB_TIMEOUT_IMMEDIATE: + err = pthread_mutex_trylock(&lock->mutex); + break; + default: + clock_gettime(CLOCK_REALTIME, &tm); + tm.tv_nsec += 1000 * (timeout%1000000); + if (tm.tv_nsec < 1000000000) + tm.tv_sec += timeout/1000000; + else { + tm.tv_sec += 1 + timeout/1000000; + tm.tv_nsec -= 1000000000; + } + err = pthread_mutex_timedlock(&lock->mutex, &tm); + } + + if (!err) + return 0; + + switch (err) { + case EOWNERDEAD: + err = pthread_mutex_consistent(&lock->mutex); + if (err) { + pcilib_error("Failed to mark mutex as consistent, errno %i", err); + } + break; + case ETIMEDOUT: + case EBUSY: + return PCILIB_ERROR_TIMEOUT; + default: + pcilib_error("Failed to obtain mutex, errno %i", err); + } + + return PCILIB_ERROR_FAILED; +} + +int pcilib_lock(pcilib_lock_t* lock) { + return pcilib_lock_custom(lock, PCILIB_LOCK_FLAGS_DEFAULT, PCILIB_TIMEOUT_INFINITE); +} + +/** + * this function will unlock the semaphore pointed by lock_ctx. + */ +void pcilib_unlock(pcilib_lock_t *lock) { + int err; + + if (!lock) + return; + + if ((err = pthread_mutex_unlock(&lock->mutex)) != 0) + pcilib_error("Can't unlock mutex, errno %i", err); +} diff --git a/pcilib/lock.h b/pcilib/lock.h new file mode 100644 index 0000000..8e1017a --- /dev/null +++ b/pcilib/lock.h @@ -0,0 +1,99 @@ +/** + * @file lock.h + * @skip author zilio nicolas, nicolas.zilio@hotmail.fr + * @brief this file is the header file for the functions that implement a semaphore API for the pcitool program, using pthread robust mutexes. + * @details the use of pthread robust mutexes was chosen due to the fact we privilege security over fastness, and that pthread mutexes permits to recover semaphores even with crash ,and that it does not require access to resources that can be easily accessible from extern usage as flock file locking mechanism. A possible other locking mechanism could be the sysv semaphores, but we have a problem of how determine a perfect hash for the init function, and more, benchmarks proves that sysv semaphore aren't that stable and that with more than 10 locks/unlocks, pthread is better in performance, so that should suits more to the final pcitool program. + * We considered that mutex implmentation is enough compared to a reader/writer implementation. If it should change, please go to sysv semaphore. + * Basic explanation on how semaphores here work: a semaphore here is a positive integer, thus that can't go below zero, which is initiated with a value. when a process want access to the critical resource, it asks to decrement the value of the semaphore, and when it has finished, it reincrements it.basically, when the semaphore is equal to zero, any process must have to wait for it to be reincremented before decrementing it again. Here are defined two types of access to the semaphore corresponding to the reader/writer problem : an exclusive lock, which means that no other process than the one who have the resource can access it; a shared lock, which means that other processes who want to access to the resource with a shared lock can have the access, but a concurrent process who want to access the semaphore with an exclusive lock won't be able to. + * explanation on locks here : here locks are registered in kernel memory, where they are defined by a pthread_mutex_t and a name, which corresponds to a register or processus. The iterations like searching a lock are done on names. + */ + +#ifndef _PCILIB_LOCK_H +#define _PCILIB_LOCK_H + +#define PCILIB_LOCK_SIZE 128 /**< size of one lock, determine so the size of the protocol_name in the way locks are registered. 40 bytes are necessary for the mutex structure, so we have a protocol name of length LOCK_SIZE-40*/ + +#include <pcilib.h> + +/** + * type that defines possible flags when locking a lock by calling pcilib_lock + */ +typedef enum { + PCILIB_LOCK_FLAGS_DEFAULT = 0, /**< Default flags */ + PCILIB_LOCK_FLAG_UNLOCKED = 1, /**< Perform operation unlocked (protected by global flock during initialization of locking subsystem) */ + PCILIB_LOCK_FLAG_PERSISTENT = 2 /**< Do not create robust mutexes, but preserve the lock across application launches */ +} pcilib_lock_flags_t; + +typedef struct pcilib_lock_s pcilib_lock_t; + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * this function initialize a new semaphore in the kernel given a name that corresponds to a specific processus if the semaphore is not already initialized given the name that permits to differentiate semaphores, and then return the integer that points to the semaphore that have been initialized or to a previously already initialized semaphore. + * @param[in] lock - pointer to lock to initialize + * @param[in] flags - flags + * @param[in] lock_id - lock identificator + * @return error code or 0 on success + */ +int pcilib_init_lock(pcilib_lock_t *lock, pcilib_lock_flags_t flags, const char *lock_id); + +/** + * this function uninitialize a lock in kernel memory and set the corresponding name to 0 + * @param[in] lock_ctx the pointer that points to the lock. + */ +void pcilib_free_lock(pcilib_lock_t *lock_ctx); + + +const char *pcilib_lock_get_name(pcilib_lock_t *lock); + + +/** + * Increment reference count. Not thread/process safe unless system supports stdatomic (gcc 4.9+). + * In this case, the access should be synchronized by the caller + * @param[in] lock - pointer to initialized lock + */ +void pcilib_lock_ref(pcilib_lock_t *lock); + +/** + * Decrement reference count. Not thread/process safe unless system supports stdatomic (gcc 4.9+). + * In this case, the access should be synchronized by the caller + * @param[in] lock - pointer to initialized lock + */ +void pcilib_lock_unref(pcilib_lock_t *lock); + +/** + * Return _approximate_ number of lock references. The crashed applications will may not unref. + * @param[in] lock - pointer to initialized lock + */ +size_t pcilib_lock_get_refs(pcilib_lock_t *lock); + +pcilib_lock_flags_t pcilib_lock_get_flags(pcilib_lock_t *lock); + +/** + * this function will take a lock for the mutex pointed by lock + * @param[in] lock the pointer to the mutex + * @param[in] flags define the type of lock wanted + * @param[in] timeout defines timeout + */ +int pcilib_lock_custom(pcilib_lock_t* lock, pcilib_lock_flags_t flags, pcilib_timeout_t timeout); + +/** + * this function will take a lock for the mutex pointed by lock + * @param[in] lock the pointer to the mutex + */ +int pcilib_lock(pcilib_lock_t* lock); + +/** + * this function will unlock the lock pointed by lock + * @param[in] lock the integer that points to the semaphore + */ +void pcilib_unlock(pcilib_lock_t* lock); + +#ifdef __cplusplus +} +#endif + +#endif /* _PCILIB_LOCK_H */ diff --git a/pcilib/locking.c b/pcilib/locking.c new file mode 100644 index 0000000..ffd05ce --- /dev/null +++ b/pcilib/locking.c @@ -0,0 +1,314 @@ +#define _XOPEN_SOURCE 700 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <assert.h> +#include <sys/file.h> + +#include "locking.h" +#include "error.h" +#include "pci.h" +#include "kmem.h" + +/* + * this function allocates the kernel memory for the locks for software registers + */ +int pcilib_init_locking(pcilib_t* ctx) { + int i; + int err; + pcilib_kmem_reuse_state_t reused; + + assert(PCILIB_LOCK_PAGES * PCILIB_KMEM_PAGE_SIZE >= PCILIB_MAX_LOCKS * PCILIB_LOCK_SIZE); + + err = pcilib_lock_global(ctx); + if (err) return err; + + ctx->locks.kmem = pcilib_alloc_kernel_memory(ctx, PCILIB_KMEM_TYPE_PAGE, PCILIB_LOCK_PAGES, PCILIB_KMEM_PAGE_SIZE, 0, PCILIB_KMEM_USE(PCILIB_KMEM_USE_LOCKS,0), PCILIB_KMEM_FLAG_REUSE|PCILIB_KMEM_FLAG_PERSISTENT); + if (!ctx->locks.kmem) { + pcilib_unlock_global(ctx); + pcilib_error("Allocation of kernel memory for locking subsystem has failed"); + return PCILIB_ERROR_FAILED; + } + + reused = pcilib_kmem_is_reused(ctx, ctx->locks.kmem); + if (reused & PCILIB_KMEM_REUSE_PARTIAL) { + pcilib_unlock_global(ctx); + pcilib_error("Inconsistent kernel memory for locking subsystem is found (only part of the required buffers is available)"); + return PCILIB_ERROR_INVALID_STATE; + } + + if ((reused & PCILIB_KMEM_REUSE_REUSED) == 0) { + for (i = 0; i < PCILIB_LOCK_PAGES; i++) { + void *addr = pcilib_kmem_get_block_ua(ctx, ctx->locks.kmem, i); + memset(addr, 0, PCILIB_KMEM_PAGE_SIZE); + } + } + + ctx->locks.locking = pcilib_get_lock(ctx, PCILIB_LOCK_FLAG_UNLOCKED, "locking"); + ctx->locks.mmap = pcilib_get_lock(ctx, PCILIB_LOCK_FLAG_UNLOCKED, "mmap"); + + pcilib_unlock_global(ctx); + + if ((!ctx->locks.locking)||(!ctx->locks.mmap)) { + pcilib_error("Locking subsystem has failed to initialized mandatory global locks"); + return PCILIB_ERROR_FAILED; + } + + return 0; +} + +/* + * this functions destroy all locks and then free the kernel memory allocated for them + */ +void pcilib_free_locking(pcilib_t *ctx) { + if (ctx->locks.mmap) + pcilib_return_lock(ctx, PCILIB_LOCK_FLAGS_DEFAULT, ctx->locks.mmap); + + if (ctx->locks.locking) + pcilib_return_lock(ctx, PCILIB_LOCK_FLAGS_DEFAULT, ctx->locks.locking); + + if (ctx->locks.kmem) { + pcilib_free_kernel_memory(ctx, ctx->locks.kmem, PCILIB_KMEM_FLAG_REUSE); + } + + memset(&ctx->locks, 0, sizeof(pcilib_locking_t)); +} + +int pcilib_lock_global(pcilib_t *ctx) { + int err; + + /* we flock() to make sure to not have two initialization in the same time (possible long time to init) */ + if ((err = flock(ctx->handle, LOCK_EX))==-1) { + pcilib_error("Can't get flock on device file"); + return PCILIB_ERROR_FAILED; + } + + return 0; +} + +void pcilib_unlock_global(pcilib_t *ctx) { + if (flock(ctx->handle, LOCK_UN) == -1) + pcilib_warning("Could not correctly remove lock from the device file"); +} + +pcilib_lock_t *pcilib_get_lock_by_id(pcilib_t *ctx, pcilib_lock_id_t id) { + int page = id / PCILIB_LOCKS_PER_PAGE; + int offset = id - page * PCILIB_LOCKS_PER_PAGE; + void *addr = pcilib_kmem_get_block_ua(ctx, ctx->locks.kmem, page); + pcilib_lock_t *lock = (pcilib_lock_t*)(addr + offset * PCILIB_LOCK_SIZE); + + return lock; +} + +pcilib_lock_t *pcilib_get_lock(pcilib_t *ctx, pcilib_lock_flags_t flags, const char *lock_id, ...) { + pcilib_lock_id_t i; + int err, ret; + + pcilib_lock_t *lock; + char buffer[PCILIB_LOCK_SIZE]; + + + va_list pa; + va_start(pa, lock_id); + ret = vsnprintf(buffer, PCILIB_LOCK_SIZE, lock_id, pa); + va_end(pa); + + if (ret < 0) { + pcilib_error("Failed to construct the lock id, probably arguments does not match the format string (%s)...", lock_id); + return NULL; + } + + // Would be nice to have hash here + for (i = 0; i < PCILIB_MAX_LOCKS; i++) { + lock = pcilib_get_lock_by_id(ctx, i); + + const char *name = pcilib_lock_get_name(lock); + if (!name) break; + + if (!strcmp(buffer, name)) { + if ((pcilib_lock_get_flags(lock)&PCILIB_LOCK_FLAG_PERSISTENT) != (flags&PCILIB_LOCK_FLAG_PERSISTENT)) { + if (flags&PCILIB_LOCK_FLAG_PERSISTENT) + pcilib_error("Requesting persistent lock (%s), but requested lock is already existing and is robust", name); + else + pcilib_error("Requesting robust lock (%s), but requested lock is already existing and is persistent", name); + return NULL; + } + +#ifndef HAVE_STDATOMIC_H + if ((flags&PCILIB_LOCK_FLAG_UNLOCKED)==0) { + err = pcilib_lock(ctx->locks.locking); + if (err) { + pcilib_error("Error (%i) obtaining global lock", err); + return NULL; + } + } +#endif /* ! HAVE_STDATOMIC_H */ + pcilib_lock_ref(lock); +#ifndef HAVE_STDATOMIC_H + if ((flags&PCILIB_LOCK_FLAG_UNLOCKED)==0) + pcilib_unlock(ctx->locks.locking); +#endif /* ! HAVE_STDATOMIC_H */ + + return lock; + } + } + + if ((flags&PCILIB_LOCK_FLAG_UNLOCKED)==0) { + err = pcilib_lock(ctx->locks.locking); + if (err) { + pcilib_error("Error (%i) obtaining global lock", err); + return NULL; + } + } + + // Make sure it was not allocated meanwhile + for (; i < PCILIB_MAX_LOCKS; i++) { + lock = pcilib_get_lock_by_id(ctx, i); + + const char *name = pcilib_lock_get_name(lock); + if (!name) break; + + if (!strcmp(buffer, name)) { + if ((pcilib_lock_get_flags(lock)&PCILIB_LOCK_FLAG_PERSISTENT) != (flags&PCILIB_LOCK_FLAG_PERSISTENT)) { + if (flags&PCILIB_LOCK_FLAG_PERSISTENT) + pcilib_error("Requesting persistent lock (%s), but requested lock is already existing and is robust", name); + else + pcilib_error("Requesting robust lock (%s), but requested lock is already existing and is persistent", name); + + if ((flags&PCILIB_LOCK_FLAG_UNLOCKED)==0) + pcilib_unlock(ctx->locks.locking); + return NULL; + } + + pcilib_lock_ref(lock); + if ((flags&PCILIB_LOCK_FLAG_UNLOCKED)==0) + pcilib_unlock(ctx->locks.locking); + return lock; + } + } + + if (i == PCILIB_MAX_LOCKS) { + if ((flags&PCILIB_LOCK_FLAG_UNLOCKED)==0) + pcilib_unlock(ctx->locks.locking); + pcilib_error("Failed to create lock (%s), only %u locks is supported", buffer, PCILIB_MAX_LOCKS); + return NULL; + } + + err = pcilib_init_lock(lock, flags, buffer); + + if (err) { + pcilib_error("Lock initialization failed with error %i", err); + + if ((flags&PCILIB_LOCK_FLAG_UNLOCKED)==0) + pcilib_unlock(ctx->locks.locking); + + return NULL; + } + + if ((flags&PCILIB_LOCK_FLAG_UNLOCKED)==0) + pcilib_unlock(ctx->locks.locking); + + return lock; +} + +void pcilib_return_lock(pcilib_t *ctx, pcilib_lock_flags_t flags, pcilib_lock_t *lock) { +#ifndef HAVE_STDATOMIC_H + int err; + + if ((flags&PCILIB_LOCK_FLAG_UNLOCKED)==0) { + err = pcilib_lock(ctx->locks.locking); + if (err) { + pcilib_error("Error (%i) obtaining global lock", err); + return; + } + } +#endif /* ! HAVE_STDATOMIC_H */ + pcilib_lock_unref(lock); +#ifndef HAVE_STDATOMIC_H + if ((flags&PCILIB_LOCK_FLAG_UNLOCKED)==0) + pcilib_unlock(ctx->locks.locking); +#endif /* ! HAVE_STDATOMIC_H */ +} + + +/** + * Destroy all existing locks. This is unsafe call as this and other running applications + * will still have all initialized lock pointers. It is user responsibility to issue this + * command when no other application is running. + */ +int pcilib_destroy_all_locks(pcilib_t *ctx, int force) { + int err; + pcilib_lock_id_t i; + pcilib_kmem_reuse_state_t reused; + + if (strcasecmp(ctx->model, "maintenance")) { + pcilib_error("Can't destroy locks while locking subsystem is initialized, use maintenance model"); + return PCILIB_ERROR_INVALID_STATE; + } + + err = pcilib_lock_global(ctx); + if (err) return err; + + // ToDo: We should check here that no other instances of pcitool are running, the driver can provide this information + + ctx->locks.kmem = pcilib_alloc_kernel_memory(ctx, PCILIB_KMEM_TYPE_PAGE, PCILIB_LOCK_PAGES, PCILIB_KMEM_PAGE_SIZE, 0, PCILIB_KMEM_USE(PCILIB_KMEM_USE_LOCKS,0), PCILIB_KMEM_FLAG_REUSE|PCILIB_KMEM_FLAG_PERSISTENT); + if (!ctx->locks.kmem) { + pcilib_unlock_global(ctx); + pcilib_error("Failed to allocate kernel memory of locking subsystem"); + return PCILIB_ERROR_FAILED; + } + + reused = pcilib_kmem_is_reused(ctx, ctx->locks.kmem); + if (reused & PCILIB_KMEM_REUSE_PARTIAL) { + pcilib_unlock_global(ctx); + pcilib_error("Inconsistent kernel memory for locking subsystem is found (only part of the required buffers is available)"); + return PCILIB_ERROR_INVALID_STATE; + } + + if ((reused & PCILIB_KMEM_REUSE_REUSED) == 0) { + pcilib_free_kernel_memory(ctx, ctx->locks.kmem, PCILIB_KMEM_FLAG_REUSE|PCILIB_KMEM_FLAG_PERSISTENT); + pcilib_unlock_global(ctx); + return 0; + } + + if (!force) { + for (i = 0; i < PCILIB_MAX_LOCKS; i++) { + pcilib_lock_t *lock = pcilib_get_lock_by_id(ctx, i); + + const char *name = pcilib_lock_get_name(lock); + if (!name) break; + + size_t refs = pcilib_lock_get_refs(lock); + + if (refs > 0) { + char *stmp = strdup(name); + pcilib_free_locking(ctx); + pcilib_unlock_global(ctx); + pcilib_error("Lock (%s) has %zu references, destroying references may result in crashes and data corruption", stmp, refs); + free(stmp); + return PCILIB_ERROR_BUSY; + } + } + } + + // Do we really need this? I guess zeroing should be enough + for (i = 0; i < PCILIB_MAX_LOCKS; i++) { + pcilib_lock_t *lock = pcilib_get_lock_by_id(ctx, i); + + const char *name = pcilib_lock_get_name(lock); + if (!name) break; + + pcilib_free_lock(lock); + } + + for (i = 0; i < PCILIB_LOCK_PAGES; i++) { + void *addr = pcilib_kmem_get_block_ua(ctx, ctx->locks.kmem, i); + memset(addr, 0, PCILIB_KMEM_PAGE_SIZE); + } + + pcilib_free_locking(ctx); + pcilib_unlock_global(ctx); + + return 0; +} diff --git a/pcilib/locking.h b/pcilib/locking.h new file mode 100644 index 0000000..ae2f368 --- /dev/null +++ b/pcilib/locking.h @@ -0,0 +1,48 @@ +/** + * @file lock_global.h + * @brief this file is the header file for functions that touch all locks allocated for software registers. + * @details for more details about implementation choice, please read the file lock.h + */ +#ifndef _PCILIB_LOCKING_H +#define _PCILIB_LOCKING_H + +#define PCILIB_MAX_LOCKS 64 /**< number of maximum locks*/ +#define PCILIB_LOCKS_PER_PAGE (PCILIB_KMEM_PAGE_SIZE/PCILIB_LOCK_SIZE) /**< number of locks per page of kernel memory */ +#define PCILIB_LOCK_PAGES ((PCILIB_MAX_LOCKS * PCILIB_LOCK_SIZE)/PCILIB_KMEM_PAGE_SIZE) /**< number of pages allocated for locks in kernel memory */ + + +#include <pcilib/kmem.h> +#include <pcilib/lock.h> + +typedef uint32_t pcilib_lock_id_t; + +typedef struct pcilib_locking_s pcilib_locking_t; +struct pcilib_locking_s { + pcilib_kmem_handle_t *kmem; /**< kmem used to store mutexes */ + pcilib_lock_t *locking; /**< lock used while intializing other locks */ + pcilib_lock_t *mmap; /**< lock used to protect mmap operation */ +}; + +#ifdef __cplusplus +extern "C" { +#endif + +int pcilib_init_locking(pcilib_t *ctx); +void pcilib_free_locking(pcilib_t *ctx); + +int pcilib_lock_global(pcilib_t *ctx); +void pcilib_unlock_global(pcilib_t *ctx); + +pcilib_lock_t *pcilib_get_lock_by_id(pcilib_t *ctx, pcilib_lock_id_t id); + +pcilib_lock_t *pcilib_get_lock(pcilib_t *ctx, pcilib_lock_flags_t flags, const char *lock_id, ...); +void pcilib_return_lock(pcilib_t *ctx, pcilib_lock_flags_t flags, pcilib_lock_t *lock); + +int pcilib_destroy_all_locks(pcilib_t *ctx, int force); + + +#ifdef __cplusplus +} +#endif + +#endif /* _PCILIB_LOCKING_H */ diff --git a/pcilib/pci.c b/pcilib/pci.c index 3a18ccd..512e891 100644 --- a/pcilib/pci.c +++ b/pcilib/pci.c @@ -1,4 +1,5 @@ //#define PCILIB_FILE_IO +#define _XOPEN_SOURCE 700 #define _BSD_SOURCE #define _POSIX_C_SOURCE 200809L @@ -24,6 +25,7 @@ #include "model.h" #include "plugin.h" #include "bar.h" +#include "locking.h" static int pcilib_detect_model(pcilib_t *ctx, const char *model) { int i, j; @@ -118,6 +120,18 @@ pcilib_t *pcilib_open(const char *device, const char *model) { } ctx->page_mask = (uintptr_t)-1; + + if ((model)&&(!strcasecmp(model, "maintenance"))) { + ctx->model = strdup("maintenance"); + return ctx; + } + + err = pcilib_init_locking(ctx); + if (err) { + pcilib_error("Error (%i) initializing locking subsystem", err); + pcilib_close(ctx); + return NULL; + } ctx->alloc_reg = PCILIB_DEFAULT_REGISTER_SPACE; ctx->registers = (pcilib_register_description_t *)malloc(PCILIB_DEFAULT_REGISTER_SPACE * sizeof(pcilib_register_description_t)); @@ -159,7 +173,6 @@ pcilib_t *pcilib_open(const char *device, const char *model) { ctx->model_info.protocols = ctx->protocols; ctx->model_info.ranges = ctx->ranges; - err = pcilib_init_register_banks(ctx); if (err) { pcilib_error("Error (%i) initializing regiser banks\n", err); @@ -335,7 +348,10 @@ void pcilib_close(pcilib_t *ctx) { if (ctx->event_plugin) pcilib_plugin_close(ctx->event_plugin); - + + if (ctx->locks.kmem) + pcilib_free_locking(ctx); + if (ctx->kmem_list) { pcilib_warning("Not all kernel buffers are properly cleaned"); @@ -360,7 +376,7 @@ void pcilib_close(pcilib_t *ctx) { if (ctx->model) free(ctx->model); - + if (ctx->handle >= 0) close(ctx->handle); diff --git a/pcilib/pci.h b/pcilib/pci.h index d176caf..c97f753 100644 --- a/pcilib/pci.h +++ b/pcilib/pci.h @@ -24,6 +24,7 @@ #include "event.h" #include "model.h" #include "export.h" +#include "locking.h" typedef struct { uint8_t max_link_speed, link_speed; @@ -70,6 +71,8 @@ struct pcilib_s { pcilib_dma_context_t *dma_ctx; /**< DMA context */ pcilib_context_t *event_ctx; /**< Implmentation context */ + struct pcilib_locking_s locks; /**< Context of locking subsystem */ + #ifdef PCILIB_FILE_IO int file_io_handle; #endif /* PCILIB_FILE_IO */ |