diff options
Diffstat (limited to 'pcilib/locking.c')
-rw-r--r-- | pcilib/locking.c | 314 |
1 files changed, 314 insertions, 0 deletions
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; +} |