diff options
-rw-r--r-- | CMakeLists.txt | 8 | ||||
-rw-r--r-- | dma/ipe.c | 100 | ||||
-rw-r--r-- | dma/ipe.h | 3 | ||||
-rw-r--r-- | dma/ipe_benchmark.c | 2 | ||||
-rw-r--r-- | dma/ipe_private.h | 12 | ||||
-rw-r--r-- | docs/README | 7 | ||||
-rw-r--r-- | docs/ToDo | 19 | ||||
-rw-r--r-- | driver/Makefile | 2 | ||||
-rw-r--r-- | driver/base.h | 10 | ||||
-rw-r--r-- | pcilib/CMakeLists.txt | 9 | ||||
-rw-r--r-- | pcilib/bar.c | 18 | ||||
-rw-r--r-- | pcilib/config.h.in | 1 | ||||
-rw-r--r-- | pcilib/dma.c | 62 | ||||
-rw-r--r-- | pcilib/event.h | 11 | ||||
-rw-r--r-- | pcilib/kmem.c | 12 | ||||
-rw-r--r-- | pcilib/kmem.h | 4 | ||||
-rw-r--r-- | pcilib/lock.c | 216 | ||||
-rw-r--r-- | pcilib/lock.h | 106 | ||||
-rw-r--r-- | pcilib/locking.c | 311 | ||||
-rw-r--r-- | pcilib/locking.h | 48 | ||||
-rw-r--r-- | pcilib/pci.c | 44 | ||||
-rw-r--r-- | pcilib/pci.h | 7 | ||||
-rw-r--r-- | pcilib/pcilib.h | 11 | ||||
-rw-r--r-- | pcitool/CMakeLists.txt | 4 | ||||
-rw-r--r-- | pcitool/cli.c | 232 | ||||
-rw-r--r-- | protocols/software.c | 47 |
26 files changed, 1152 insertions, 154 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b5a498..fbbc383 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ project(pcitool) -set(PCILIB_VERSION "0.2.0") +set(PCILIB_VERSION "0.2.1") set(PCILIB_ABI_VERSION "2") cmake_minimum_required(VERSION 2.6) @@ -13,20 +13,22 @@ find_package(PkgConfig REQUIRED) find_package(Threads REQUIRED) find_package(PythonLibs REQUIRED) +set(EXTRA_SYSTEM_LIBS -lrt) + #Check in sibling directory if (NOT DISABLE_PCITOOL) pkg_check_modules(FASTWRITER fastwriter REQUIRED) endif (NOT DISABLE_PCITOOL) pkg_check_modules(XMLLIB libxml-2.0 REQUIRED) +check_include_files(stdatomic.h HAVE_STDATOMIC_H) -add_definitions("-fPIC --std=c99 -Wall -O2 -gdwarf-2 -g3 -g") +add_definitions("-fPIC --std=c99 -Wall -O2 -gdwarf-2 -g3 -fno-omit-frame-pointer") #add_definitions("-fPIC --std=c99 -Wall -O2") include(cmake/version.cmake) VERSION_TO_VARS(${PCILIB_VERSION} PCILIB_VERSION_MAJOR PCILIB_VERSION_MINOR PCILIB_VERSION_MICRO) - add_subdirectory(dma) add_subdirectory(protocols) add_subdirectory(pcilib) @@ -22,8 +22,8 @@ pcilib_dma_context_t *dma_ipe_init(pcilib_t *pcilib, const char *model, const void *arg) { -// int err = 0; - + pcilib_register_value_t value; + const pcilib_model_description_t *model_info = pcilib_get_model_description(pcilib); ipe_dma_t *ctx = malloc(sizeof(ipe_dma_t)); @@ -32,11 +32,6 @@ pcilib_dma_context_t *dma_ipe_init(pcilib_t *pcilib, const char *model, const vo memset(ctx, 0, sizeof(ipe_dma_t)); ctx->dmactx.pcilib = pcilib; -#ifdef IPEDMA_64BIT_MODE - // Always supported and we need to use it - ctx->mode64 = 1; -#endif /* IPEDMA_64BIT_MODE */ - pcilib_register_bank_t dma_bank = pcilib_find_register_bank_by_addr(pcilib, PCILIB_REGISTER_BANK_DMA); if (dma_bank == PCILIB_REGISTER_BANK_INVALID) { @@ -47,6 +42,19 @@ pcilib_dma_context_t *dma_ipe_init(pcilib_t *pcilib, const char *model, const vo ctx->dma_bank = model_info->banks + dma_bank; ctx->base_addr = pcilib_resolve_register_address(pcilib, ctx->dma_bank->bar, ctx->dma_bank->read_addr); + + RD(IPEDMA_REG_PCIE_GEN, value); + +#ifdef IPEDMA_ENFORCE_64BIT_MODE + ctx->mode64 = 1; +#else /* IPEDMA_ENFORCE_64BIT_MODE */ + // According to Lorenzo, some gen2 boards have problems with 64-bit addressing. Therefore, we only enable it for gen3 boards unless enforced + if ((value&IPEDMA_MASK_PCIE_GEN) > 2) ctx->mode64 = 1; +#endif /* IPEDMA_ENFORCE_64BIT_MODE */ + +#ifdef IPEDMA_STREAMING_MODE + if (value&IPEDMA_MASK_STREAMING_MODE) ctx->streaming = 1; +#endif /* IPEDMA_STREAMING_MODE */ } return (pcilib_dma_context_t*)ctx; @@ -63,7 +71,7 @@ void dma_ipe_free(pcilib_dma_context_t *vctx) { int dma_ipe_start(pcilib_dma_context_t *vctx, pcilib_dma_engine_t dma, pcilib_dma_flags_t flags) { - size_t i; + size_t i, num_pages; ipe_dma_t *ctx = (ipe_dma_t*)vctx; @@ -110,15 +118,16 @@ int dma_ipe_start(pcilib_dma_context_t *vctx, pcilib_dma_engine_t dma, pcilib_dm if ((reuse_desc & PCILIB_KMEM_REUSE_PERSISTENT) == 0) pcilib_warning("Lost DMA buffers are found (non-persistent mode), reinitializing..."); else if ((reuse_desc & PCILIB_KMEM_REUSE_HARDWARE) == 0) pcilib_warning("Lost DMA buffers are found (missing HW reference), reinitializing..."); else { -#ifndef IPEDMA_BUG_DMARD -# ifndef IPEDMA_STREAMING_MODE - RD(IPEDMA_REG_PAGE_COUNT, value); - - if (value != IPEDMA_DMA_PAGES) pcilib_warning("Inconsistent DMA buffers are found (Number of allocated buffers (%lu) does not match current request (%lu)), reinitializing...", value + 1, IPEDMA_DMA_PAGES); - else -# endif /* IPEDMA_STREAMING_MODE */ -#endif /* IPEDMA_BUG_DMARD */ + if (ctx->streaming) preserve = 1; + else { + RD(IPEDMA_REG_PAGE_COUNT, value); + + if (value != IPEDMA_DMA_PAGES) + pcilib_warning("Inconsistent DMA buffers are found (Number of allocated buffers (%lu) does not match current request (%lu)), reinitializing...", value + 1, IPEDMA_DMA_PAGES); + else + preserve = 1; + } } } } else pcilib_warning("Inconsistent DMA buffers (modes of ring and page buffers does not match), reinitializing...."); @@ -132,12 +141,6 @@ int dma_ipe_start(pcilib_dma_context_t *vctx, pcilib_dma_engine_t dma, pcilib_dm ctx->preserve = 1; // Detect the current state of DMA engine -#ifdef IPEDMA_BUG_DMARD - FILE *f = fopen("/tmp/pcitool_lastread", "r"); - if (!f) pcilib_error("Can't read current status"); - fread(&value, 1, sizeof(pcilib_register_value_t), f); - fclose(f); -#else /* IPEDMA_BUG_DMARD */ RD(IPEDMA_REG_LAST_READ, value); // Numbered from 1 in FPGA # ifdef IPEDMA_BUG_LAST_READ @@ -146,7 +149,6 @@ int dma_ipe_start(pcilib_dma_context_t *vctx, pcilib_dma_engine_t dma, pcilib_dm # else /* IPEDMA_BUG_LAST_READ */ value--; # endif /* IPEDMA_BUG_LAST_READ */ -#endif /* IPEDMA_BUG_DMARD */ ctx->last_read = value; } else { @@ -162,12 +164,10 @@ int dma_ipe_start(pcilib_dma_context_t *vctx, pcilib_dma_engine_t dma, pcilib_dm WR(IPEDMA_REG_RESET, 0x0); usleep(IPEDMA_RESET_DELAY); -#ifndef IPEDMA_BUG_DMARD // Verify PCIe link status RD(IPEDMA_REG_RESET, value); if ((value != 0x14031700)&&(value != 0x14021700)) pcilib_warning("PCIe is not ready, code is %lx", value); -#endif /* IPEDMA_BUG_DMARD */ // Enable 64 bit addressing and configure TLP and PACKET sizes (40 bit mode can be used with big pre-allocated buffers later) if (ctx->mode64) address64 = 0x8000 | (0<<24); @@ -203,7 +203,13 @@ int dma_ipe_start(pcilib_dma_context_t *vctx, pcilib_dma_engine_t dma, pcilib_dm // Instructing DMA engine that writting should start from the first DMA page *last_written_addr_ptr = 0; - for (i = 0; i < IPEDMA_DMA_PAGES; i++) { + // In ring buffer mode, the hardware taking care to preserve an empty buffer to help distinguish between + // completely empty and completely full cases. In streaming mode, it is our responsibility to track this + // information. Therefore, we always keep the last buffer free + num_pages = IPEDMA_DMA_PAGES; + if (ctx->streaming) num_pages--; + + for (i = 0; i < num_pages; i++) { uintptr_t bus_addr_check, bus_addr = pcilib_kmem_get_block_ba(ctx->dmactx.pcilib, pages, i); WR(IPEDMA_REG_PAGE_ADDR, bus_addr); if (bus_addr%4096) printf("Bad address %lu: %lx\n", i, bus_addr); @@ -220,14 +226,6 @@ int dma_ipe_start(pcilib_dma_context_t *vctx, pcilib_dma_engine_t dma, pcilib_dm WR(IPEDMA_REG_CONTROL, 0x1); ctx->last_read = IPEDMA_DMA_PAGES - 1; - -#ifdef IPEDMA_BUG_DMARD - FILE *f = fopen("/tmp/pcitool_lastread", "w"); - if (!f) pcilib_error("Can't write current status"); - value = ctx->last_read; - fwrite(&value, 1, sizeof(pcilib_register_value_t), f); - fclose(f); -#endif /* IPEDMA_BUG_DMARD */ } ctx->last_read_addr = pcilib_kmem_get_block_ba(ctx->dmactx.pcilib, pages, ctx->last_read); @@ -411,10 +409,6 @@ int dma_ipe_stream_read(pcilib_dma_context_t *vctx, pcilib_dma_engine_t dma, uin pcilib_dma_flags_t packet_flags = PCILIB_DMA_FLAG_EOP; size_t nodata_sleep; -#ifdef IPEDMA_BUG_DMARD - pcilib_register_value_t value; -#endif /* IPEDMA_BUG_DMARD */ - switch (sched_getscheduler(0)) { case SCHED_FIFO: case SCHED_RR: @@ -507,16 +501,21 @@ int dma_ipe_stream_read(pcilib_dma_context_t *vctx, pcilib_dma_engine_t dma, uin // pcilib_kmem_sync_block(ctx->dmactx.pcilib, ctx->pages, PCILIB_KMEM_SYNC_TODEVICE, cur_read); // Return buffer into the DMA pool when processed -#ifdef IPEDMA_STREAMING_MODE - uintptr_t buf_ba = pcilib_kmem_get_block_ba(ctx->dmactx.pcilib, ctx->pages, cur_read); - WR(IPEDMA_REG_PAGE_ADDR, buf_ba); + if (ctx->streaming) { + size_t last_free; + // We always keep 1 buffer free to distinguish between completely full and empty cases + if (cur_read) last_free = cur_read - 1; + else last_free = IPEDMA_DMA_PAGES - 1; + + uintptr_t buf_ba = pcilib_kmem_get_block_ba(ctx->dmactx.pcilib, ctx->pages, last_free); + WR(IPEDMA_REG_PAGE_ADDR, buf_ba); # ifdef IPEDMA_STREAMING_CHECKS - pcilib_register_value_t streaming_status; - RD(IPEDMA_REG_STREAMING_STATUS, streaming_status); - if (streaming_status) - pcilib_error("Invalid status (0x%lx) adding a DMA buffer into the queue", streaming_status); + pcilib_register_value_t streaming_status; + RD(IPEDMA_REG_STREAMING_STATUS, streaming_status); + if (streaming_status) + pcilib_error("Invalid status (0x%lx) adding a DMA buffer into the queue", streaming_status); # endif /* IPEDMA_STREAMING_MODE */ -#endif /* IPEDMA_STREAMING_MODE */ + } // Numbered from 1 #ifdef IPEDMA_BUG_LAST_READ @@ -533,15 +532,6 @@ int dma_ipe_stream_read(pcilib_dma_context_t *vctx, pcilib_dma_engine_t dma, uin ctx->last_read = cur_read; ctx->last_read_addr = pcilib_kmem_get_block_ba(ctx->dmactx.pcilib, ctx->pages, cur_read); - -#ifdef IPEDMA_BUG_DMARD - FILE *f = fopen("/tmp/pcitool_lastread", "w"); - if (!f) pcilib_error("Can't write current status"); - value = cur_read; - fwrite(&value, 1, sizeof(pcilib_register_value_t), f); - fclose(f); -#endif /* IPEDMA_BUG_DMARD */ - } while (ret); return 0; @@ -65,6 +65,9 @@ static const pcilib_register_description_t ipe_dma_registers[] = { {0x000C, 24, 8, 0, 0xFFFFFFFF, PCILIB_REGISTER_RW , PCILIB_REGISTER_BITS, PCILIB_REGISTER_BANK_DMA, "mwr_up_addr", "Upper address for 64 bit memory addressing"}, {0x0010, 0, 32, 0, 0x00000000, PCILIB_REGISTER_RW , PCILIB_REGISTER_STANDARD, PCILIB_REGISTER_BANK_DMA, "mwr_count", "Write DMA TLP Count"}, {0x0014, 0, 32, 0, 0x00000000, PCILIB_REGISTER_RW , PCILIB_REGISTER_STANDARD, PCILIB_REGISTER_BANK_DMA, "mwr_pattern", "DMA generator data pattern"}, + {0x0018, 0, 32, 0, 0x00000000, PCILIB_REGISTER_R , PCILIB_REGISTER_STANDARD, PCILIB_REGISTER_BANK_DMA, "dma_mode_flags", "DMA operation mode"}, + {0x0018, 0, 4, 0, 0x00000000, PCILIB_REGISTER_R , PCILIB_REGISTER_BITS, PCILIB_REGISTER_BANK_DMA, "pcie_gen", "PCIe version 2/3 depending on the used XILINX core"}, + {0x0018, 4, 1, 0, 0x00000000, PCILIB_REGISTER_R , PCILIB_REGISTER_BITS, PCILIB_REGISTER_BANK_DMA, "streaming_dma", "Streaming mode (enabled/disabled)"}, {0x0028, 0, 32, 0, 0x00000000, PCILIB_REGISTER_R , PCILIB_REGISTER_STANDARD, PCILIB_REGISTER_BANK_DMA, "mwr_perf", "MWR Performance"}, {0x003C, 0, 32, 0, 0x00000000, PCILIB_REGISTER_R , PCILIB_REGISTER_STANDARD, PCILIB_REGISTER_BANK_DMA, "cfg_lnk_width", "Negotiated and max width of PCIe Link"}, {0x003C, 0, 6, 0, 0x00000000, PCILIB_REGISTER_R , PCILIB_REGISTER_BITS, PCILIB_REGISTER_BANK_DMA, "cfg_cap_max_lnk_width", "Max link width"}, diff --git a/dma/ipe_benchmark.c b/dma/ipe_benchmark.c index f8e0a7e..3ce04d3 100644 --- a/dma/ipe_benchmark.c +++ b/dma/ipe_benchmark.c @@ -166,7 +166,7 @@ double dma_ipe_benchmark(pcilib_dma_context_t *vctx, pcilib_dma_engine_addr_t dm for (bytes = 0; bytes < size; bytes += rbytes) { err = read_dma(ctx->dmactx.pcilib, 0, addr, size - bytes, PCILIB_DMA_FLAG_MULTIPACKET, PCILIB_DMA_TIMEOUT, buf + bytes, &rbytes); if (err) { - pcilib_error("Can't read data from DMA, error %i", err); + pcilib_error("Can't read data from DMA (iteration: %zu, offset: %zu), error %i", iter, bytes, err); return -1; } } diff --git a/dma/ipe_private.h b/dma/ipe_private.h index 249286d..5054a58 100644 --- a/dma/ipe_private.h +++ b/dma/ipe_private.h @@ -3,13 +3,13 @@ #include "dma.h" -#define IPEDMA_64BIT_MODE 1 /**< 64-bit mode addressing is required to support PCIe gen3 */ +//#define IPEDMA_ENFORCE_64BIT_MODE 1 /**< enforce 64-bit mode addressing (otherwise it is used only if register 0x18 specifies PCIe gen3 as required by DMA engine) */ #define IPEDMA_CORES 1 #define IPEDMA_MAX_TLP_SIZE 256 /**< Defines maximum TLP in bytes supported by device */ -//#define IPEDMA_TLP_SIZE 128 /**< If set, enforces the specified TLP size */ +//#define IPEDMA_TLP_SIZE 128 /**< If set, enforces the specified TLP size */ #define IPEDMA_STREAMING_MODE /**< Enables streaming DMA operation mode instead of ring-buffer, the page is written once and forgotten and need to be pushed in queue again */ -#define IPEDMA_STREAMING_CHECKS /**< Enables status checks in streaming mode (it will cause performance penalty) */ +//#define IPEDMA_STREAMING_CHECKS /**< Enables status checks in streaming mode (it will cause _significant_ performance penalty, max ~ 2 GB/s) */ #define IPEDMA_PAGE_SIZE 4096 #define IPEDMA_DMA_PAGES 1024 /**< number of DMA pages in the ring buffer to allocate */ #define IPEDMA_DMA_PROGRESS_THRESHOLD 1 /**< how many pages the DMA engine should fill before reporting progress */ @@ -17,7 +17,6 @@ #define IPEDMA_DESCRIPTOR_ALIGNMENT 64 -//#define IPEDMA_BUG_DMARD /**< No register read during DMA transfer */ //#define IPEDMA_BUG_LAST_READ /**< We should forbid writting the second last available DMA buffer (the last is forbidden by design) */ //#define IPEDMA_DETECT_PACKETS /**< Using empty_deceted flag */ #define IPEDMA_SUPPORT_EMPTY_DETECTED /**< Avoid waiting for data when empty_detected flag is set in hardware */ @@ -31,6 +30,7 @@ #define IPEDMA_REG_CONTROL 0x04 #define IPEDMA_REG_TLP_SIZE 0x0C #define IPEDMA_REG_TLP_COUNT 0x10 +#define IPEDMA_REG_PCIE_GEN 0x18 #define IPEDMA_REG_PAGE_ADDR 0x50 #define IPEDMA_REG_UPDATE_ADDR 0x54 #define IPEDMA_REG_LAST_READ 0x58 /**< In streaming mode, we can use it freely to track current status */ @@ -38,6 +38,9 @@ #define IPEDMA_REG_UPDATE_THRESHOLD 0x60 #define IPEDMA_REG_STREAMING_STATUS 0x68 +#define IPEDMA_MASK_PCIE_GEN 0xF +#define IPEDMA_MASK_STREAMING_MODE 0x10 + #define WR(addr, value) { *(uint32_t*)(ctx->base_addr + addr) = value; } #define RD(addr, value) { value = *(uint32_t*)(ctx->base_addr + addr); } @@ -61,6 +64,7 @@ struct ipe_dma_s { int reused; /**< indicates that DMA was found intialized, buffers were reused, and no additional initialization is needed */ int preserve; /**< indicates that DMA should not be stopped during clean-up */ int mode64; /**< indicates 64-bit operation mode */ + int streaming; /**< indicates if DMA is operating in streaming or ring-buffer mode */ pcilib_kmem_handle_t *desc; /**< in-memory status descriptor written by DMA engine upon operation progess */ pcilib_kmem_handle_t *pages; /**< collection of memory-locked pages for DMA operation */ diff --git a/docs/README b/docs/README index 647df41..335ebef 100644 --- a/docs/README +++ b/docs/README @@ -1,13 +1,16 @@ Supported environmental variables ================================= - PCILIB_PLUGIN_DIR - override path to directory with plugins + PCILIB_MODEL - defines the requested model (is overriden with -m option) + PCILIB_PLUGIN_DIR - override path to directory with plugins - PCILIB_DEBUG_DMA - Enable DMA debugging + PCILIB_DEBUG_DMA - Enable DMA debugging PCILIB_DEBUG_MISSING_EVENTS - Enable debugging of missing events (frames for instance) IPECAMERA_DEBUG_BROKEN_FRAMES - Store broken frames in the specified directory (variable may specify directory) IPECAMERA_DEBUG_RAW_PACKETS - Store all raw packets read from DMA grouped in frames (variable may specify directory) IPECAMERA_DEBUG_RAW_FRAMES - Store all raw frames (variable may specify directory) IPECAMERA_DEBUG_HARDWARE - Produce various debugging information about ipecamera operation + IPECAMERA_DEBUG_FRAME_HEADERS - Print headers of each frame + IPECAMERA_DEBUG_API - Print messages while entering and finishing standard API calls PCILIB_BENCHMARK_HARDWARE - Remove all unnecessary software processing (like copying memcpy) to check hardware performance PCILIB_BENCHMARK_STREAMING - Emulate streaming mode while benchmarking DMA engines @@ -1,23 +1,24 @@ High Priority (we would need it for IPE Camera) ============= - 1. Serialize access to the registers across applications - 2. Protect kmem_entries in the driver using spinlock - 3. Protect mmap operation (multiple kernel calls) with some locking mechanism - 4. Allow overriding of registers and banks (performance?). + 1. Allow overriding of registers and banks (performance?). Normal Priority (it would make just few things a bit easier) =============== - 1. Implement software registers (stored in kernel-memory) - 2. Implement pcilib_configure_autotrigger - 3. Provide OR and AND operations on registers in cli - 4. Support writting a data from a binary file in cli + 1. Implement pcilib_configure_autotrigger + 2. Provide OR and AND operations on registers in cli + 3. Support writting a data from a binary file in cli Low Priority (only as generalization for other projects) ============ 1. XML configurations describing registers (and DMA engines?) - 2. Access register/bank lookups using hash tables + 2. Access register/bank/lock lookups using hash tables 3. Support for Network Registers and Network DMA 4. Define a syntax for register dependencies / delays (?) 5. Use pthread_condition_t instead of polling 6. Support FIFO reads/writes from/to registers + 7. We managed kmem performance using next kmem prediction, but it is still wise to provide additionally a binary tree for faster search +Performance +=========== + 1. Even with fully algined data, glibc defaults to __memcpy_sse2_unaligned (called from ipecamera_data_callback and fastwriter_push). Can we do something? +
\ No newline at end of file diff --git a/driver/Makefile b/driver/Makefile index e353228..3a242dd 100644 --- a/driver/Makefile +++ b/driver/Makefile @@ -3,7 +3,7 @@ obj-m := pciDriver.o pciDriver-objs := base.o int.o umem.o kmem.o sysfs.o ioctl.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build -INSTALLDIR ?= /lib/modules/$(shell uname -r)/extra +INSTALLDIR ?= /lib/modules/$(shell uname -r)/kernel/extra PWD := $(shell pwd) EXTRA_CFLAGS += -I$(M)/.. diff --git a/driver/base.h b/driver/base.h index 9384e2d..b976d3f 100644 --- a/driver/base.h +++ b/driver/base.h @@ -68,14 +68,14 @@ static dev_t pcidriver_devt; static atomic_t pcidriver_deviceCount; /* Sysfs attributes */ -static DEVICE_ATTR(mmap_mode, (S_IRUGO | S_IWUGO), pcidriver_show_mmap_mode, pcidriver_store_mmap_mode); -static DEVICE_ATTR(mmap_area, (S_IRUGO | S_IWUGO), pcidriver_show_mmap_area, pcidriver_store_mmap_area); +static DEVICE_ATTR(mmap_mode, 0664, pcidriver_show_mmap_mode, pcidriver_store_mmap_mode); +static DEVICE_ATTR(mmap_area, 0664, pcidriver_show_mmap_area, pcidriver_store_mmap_area); static DEVICE_ATTR(kmem_count, S_IRUGO, pcidriver_show_kmem_count, NULL); static DEVICE_ATTR(kbuffers, S_IRUGO, pcidriver_show_kbuffers, NULL); -static DEVICE_ATTR(kmem_alloc, S_IWUGO, NULL, pcidriver_store_kmem_alloc); -static DEVICE_ATTR(kmem_free, S_IWUGO, NULL, pcidriver_store_kmem_free); +static DEVICE_ATTR(kmem_alloc, 0220, NULL, pcidriver_store_kmem_alloc); +static DEVICE_ATTR(kmem_free, 0220, NULL, pcidriver_store_kmem_free); static DEVICE_ATTR(umappings, S_IRUGO, pcidriver_show_umappings, NULL); -static DEVICE_ATTR(umem_unmap, S_IWUGO, NULL, pcidriver_store_umem_unmap); +static DEVICE_ATTR(umem_unmap, 0220, NULL, pcidriver_store_umem_unmap); #ifdef ENABLE_IRQ static DEVICE_ATTR(irq_count, S_IRUGO, pcidriver_show_irq_count, NULL); diff --git a/pcilib/CMakeLists.txt b/pcilib/CMakeLists.txt index 881b44c..774ebe9 100644 --- a/pcilib/CMakeLists.txt +++ b/pcilib/CMakeLists.txt @@ -5,9 +5,10 @@ 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 xml.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 xml.c) -target_link_libraries(pcilib dma protocols ${CMAKE_THREAD_LIBS_INIT} ${UFODECODE_LIBRARIES} ${CMAKE_DL_LIBS} ${XMLLIB_LIBRARIES} ${PYTHON_LIBRARIES}) +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} ${EXTRA_SYSTEM_LIBS} ${XMLLIB_LIBRARIES} ${PYTHON_LIBRARIES}) + add_dependencies(pcilib dma protocols) install(TARGETS pcilib @@ -18,6 +19,6 @@ install(FILES pcilib.h DESTINATION include ) -install(FILES bar.h kmem.h bank.h register.h dma.h event.h model.h error.h debug.h env.h tools.h export.h version.h xml.h +install(FILES bar.h kmem.h locking.h lock.h bank.h register.h dma.h event.h model.h error.h debug.h env.h tools.h export.h version.h DESTINATION include/pcilib ) diff --git a/pcilib/bar.c b/pcilib/bar.c index 2c7a8d4..2666506 100644 --- a/pcilib/bar.c +++ b/pcilib/bar.c @@ -73,21 +73,29 @@ int pcilib_detect_address(pcilib_t *ctx, pcilib_bar_t *bar, uintptr_t *addr, siz void *pcilib_map_bar(pcilib_t *ctx, pcilib_bar_t bar) { void *res; - int ret; + int err, ret; const pcilib_board_info_t *board_info = pcilib_get_board_info(ctx); if (!board_info) return NULL; - + if (ctx->bar_space[bar]) return ctx->bar_space[bar]; - + + err = pcilib_lock_global(ctx); + if (err) { + pcilib_error("Error (%i) acquiring mmap lock", err); + return NULL; + } + ret = ioctl( ctx->handle, PCIDRIVER_IOC_MMAP_MODE, PCIDRIVER_MMAP_PCI ); if (ret) { + pcilib_unlock_global(ctx); pcilib_error("PCIDRIVER_IOC_MMAP_MODE ioctl have failed", bar); return NULL; } ret = ioctl( ctx->handle, PCIDRIVER_IOC_MMAP_AREA, PCIDRIVER_BAR0 + bar ); if (ret) { + pcilib_unlock_global(ctx); pcilib_error("PCIDRIVER_IOC_MMAP_AREA ioctl have failed for bank %i", bar); return NULL; } @@ -98,12 +106,14 @@ void *pcilib_map_bar(pcilib_t *ctx, pcilib_bar_t bar) { #else res = mmap( 0, board_info->bar_length[bar], PROT_WRITE | PROT_READ, MAP_SHARED, ctx->handle, 0 ); #endif + + pcilib_unlock_global(ctx); + if ((!res)||(res == MAP_FAILED)) { pcilib_error("Failed to mmap data bank %i", bar); return NULL; } - return res; } 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/dma.c b/pcilib/dma.c index f6b8053..be02c21 100644 --- a/pcilib/dma.c +++ b/pcilib/dma.c @@ -61,8 +61,11 @@ pcilib_dma_engine_t pcilib_add_dma_engine(pcilib_t *ctx, pcilib_dma_engine_descr int pcilib_init_dma(pcilib_t *ctx) { int err; pcilib_dma_context_t *dma_ctx = NULL; + const pcilib_dma_description_t *info = &ctx->dma; const pcilib_model_description_t *model_info = pcilib_get_model_description(ctx); + pcilib_dma_engine_t dma; + if (ctx->dma_ctx) return 0; @@ -86,6 +89,24 @@ int pcilib_init_dma(pcilib_t *ctx) { } if (dma_ctx) { + for (dma = 0; info->engines[dma].addr_bits; dma++) { + if (info->engines[dma].direction&PCILIB_DMA_FROM_DEVICE) { + ctx->dma_rlock[dma] = pcilib_get_lock(ctx, PCILIB_LOCK_FLAGS_DEFAULT, "dma%ir/%s", info->engines[dma].addr, info->name); + if (!ctx->dma_rlock[dma]) break; + } + if (info->engines[dma].direction&PCILIB_DMA_TO_DEVICE) { + ctx->dma_wlock[dma] = pcilib_get_lock(ctx, PCILIB_LOCK_FLAGS_DEFAULT, "dma%iw/%s", info->engines[dma].addr, info->name); + if (!ctx->dma_wlock[dma]) break; + } + } + + if (info->engines[dma].addr_bits) { + if (ctx->dma.api->free) + ctx->dma.api->free(dma_ctx); + pcilib_error("Failed to intialize DMA locks"); + return PCILIB_ERROR_FAILED; + } + dma_ctx->pcilib = ctx; // DS: parameters? ctx->dma_ctx = dma_ctx; @@ -105,11 +126,11 @@ int pcilib_start_dma(pcilib_t *ctx, pcilib_dma_engine_t dma, pcilib_dma_flags_t pcilib_error("DMA Engine is not configured in the current model"); return PCILIB_ERROR_NOTAVAILABLE; } - + if (!info->api->start_dma) { return 0; } - + return info->api->start_dma(ctx->dma_ctx, dma, flags); } @@ -201,6 +222,7 @@ static int pcilib_dma_skip_callback(void *arg, pcilib_dma_flags_t flags, size_t } int pcilib_stream_dma(pcilib_t *ctx, pcilib_dma_engine_t dma, uintptr_t addr, size_t size, pcilib_dma_flags_t flags, pcilib_timeout_t timeout, pcilib_dma_callback_t cb, void *cbattr) { + int err; const pcilib_dma_description_t *info = pcilib_get_dma_description(ctx); if (!info) { pcilib_error("DMA is not supported by the device"); @@ -228,7 +250,21 @@ int pcilib_stream_dma(pcilib_t *ctx, pcilib_dma_engine_t dma, uintptr_t addr, si return PCILIB_ERROR_NOTSUPPORTED; } - return info->api->stream(ctx->dma_ctx, dma, addr, size, flags, timeout, cb, cbattr); + err = pcilib_try_lock(ctx->dma_rlock[dma]); + if (err) { + if ((err == PCILIB_ERROR_BUSY)||(err == PCILIB_ERROR_TIMEOUT)) + pcilib_error("DMA engine (%i) is busy", dma); + else + pcilib_error("Error (%i) locking DMA engine (%i)", err, dma); + + return err; + } + + err = info->api->stream(ctx->dma_ctx, dma, addr, size, flags, timeout, cb, cbattr); + + pcilib_unlock(ctx->dma_rlock[dma]); + + return err; } int pcilib_read_dma_custom(pcilib_t *ctx, pcilib_dma_engine_t dma, uintptr_t addr, size_t size, pcilib_dma_flags_t flags, pcilib_timeout_t timeout, void *buf, size_t *read_bytes) { @@ -278,6 +314,8 @@ int pcilib_skip_dma(pcilib_t *ctx, pcilib_dma_engine_t dma) { int pcilib_push_dma(pcilib_t *ctx, pcilib_dma_engine_t dma, uintptr_t addr, size_t size, pcilib_dma_flags_t flags, pcilib_timeout_t timeout, void *buf, size_t *written) { + int err; + const pcilib_dma_description_t *info = pcilib_get_dma_description(ctx); if (!info) { pcilib_error("DMA is not supported by the device"); @@ -304,8 +342,22 @@ int pcilib_push_dma(pcilib_t *ctx, pcilib_dma_engine_t dma, uintptr_t addr, size pcilib_error("The selected engine (%i) is C2S-only and does not support writes", dma); return PCILIB_ERROR_NOTSUPPORTED; } - - return info->api->push(ctx->dma_ctx, dma, addr, size, flags, timeout, buf, written); + + err = pcilib_try_lock(ctx->dma_wlock[dma]); + if (err) { + if (err == PCILIB_ERROR_BUSY) + pcilib_error("DMA engine (%i) is busy", dma); + else + pcilib_error("Error (%i) locking DMA engine (%i)", err, dma); + + return err; + } + + err = info->api->push(ctx->dma_ctx, dma, addr, size, flags, timeout, buf, written); + + pcilib_unlock(ctx->dma_wlock[dma]); + + return err; } diff --git a/pcilib/event.h b/pcilib/event.h index d2b9793..f14abeb 100644 --- a/pcilib/event.h +++ b/pcilib/event.h @@ -45,17 +45,6 @@ typedef struct { const char *description; } pcilib_event_data_type_description_t; -typedef enum { - PCILIB_STREAMING_STOP = 0, /**< stop streaming */ - PCILIB_STREAMING_CONTINUE = 1, /**< wait the default DMA timeout for a new data */ - PCILIB_STREAMING_WAIT = 2, /**< wait the specified timeout for a new data */ - PCILIB_STREAMING_CHECK = 3, /**< do not wait for the data, bail out imideatly if no data ready */ - PCILIB_STREAMING_FAIL = 4, /**< fail if data is not available on timeout */ - PCILIB_STREAMING_REQ_FRAGMENT = 5, /**< only fragment of a packet is read, wait for next fragment and fail if no data during DMA timeout */ - PCILIB_STREAMING_REQ_PACKET = 6, /**< wait for next packet and fail if no data during the specified timeout */ - PCILIB_STREAMING_TIMEOUT_MASK = 3 /**< mask specifying all timeout modes */ -} pcilib_streaming_action_t; - /* * get_data: This call is used by get_data and copy_data functions of public * interface. When copy_data is the caller, the data parameter will be passed. diff --git a/pcilib/kmem.c b/pcilib/kmem.c index 8560eae..b1d2c5c 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_global(ctx); + if (err) { + pcilib_error("Error (%i) acquiring mmap lock", err); + return NULL; + } + ret = ioctl( ctx->handle, PCIDRIVER_IOC_MMAP_MODE, PCIDRIVER_MMAP_KMEM ); if (ret) { + pcilib_unlock_global(ctx); 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_global(ctx); + + //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..3dff625 100644 --- a/pcilib/kmem.h +++ b/pcilib/kmem.h @@ -14,7 +14,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 +32,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..03a2377 --- /dev/null +++ b/pcilib/lock.c @@ -0,0 +1,216 @@ +#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) { + pcilib_error("The null lock pointer is passed to lock function"); + return PCILIB_ERROR_INVALID_ARGUMENT; + } + + 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; + } + return 0; + 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); +} + +int pcilib_try_lock(pcilib_lock_t* lock) { + return pcilib_lock_custom(lock, PCILIB_LOCK_FLAGS_DEFAULT, PCILIB_TIMEOUT_IMMEDIATE); +} + +/** + * 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) { + switch (err) { + case EPERM: + pcilib_error("Trying to unlock not locked mutex (%s) or the mutex which was locked by a different thread", lock->name); + break; + default: + 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..9ffe4cf --- /dev/null +++ b/pcilib/lock.h @@ -0,0 +1,106 @@ +/** + * @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 try to take a lock for the mutex pointed by lock + * @param[in] lock the pointer to the mutex + */ +int pcilib_try_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..f384ca4 --- /dev/null +++ b/pcilib/locking.c @@ -0,0 +1,311 @@ +#define _XOPEN_SOURCE 700 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <assert.h> +#include <stdint.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"); + + pcilib_unlock_global(ctx); + + if ((!ctx->locks.locking)) { + 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.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..ccacd63 --- /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 76bd59f..f043257 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 @@ -25,6 +26,7 @@ #include "plugin.h" #include "bar.h" #include "xml.h" +#include "locking.h" static int pcilib_detect_model(pcilib_t *ctx, const char *model) { int i, j; @@ -127,6 +129,9 @@ pcilib_t *pcilib_open(const char *device, const char *model) { if(number_banks)banks=calloc((number_banks),sizeof(pcilib_register_bank_description_t)); else pcilib_error("no banks in the xml file"); + if (!model) + model = getenv("PCILIB_MODEL"); + if (ctx) { memset(ctx, 0, sizeof(pcilib_t)); ctx->pci_cfg_space_fd = -1; @@ -139,6 +144,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)); @@ -191,7 +208,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); @@ -361,6 +377,7 @@ void pcilib_close(pcilib_t *ctx) { pcilib_bar_t i; if (ctx) { + pcilib_dma_engine_t dma; const pcilib_model_description_t *model_info = pcilib_get_model_description(ctx); const pcilib_event_api_description_t *eapi = model_info->api; const pcilib_dma_api_description_t *dapi = ctx->dma.api; @@ -368,6 +385,13 @@ void pcilib_close(pcilib_t *ctx) { if ((eapi)&&(eapi->free)) eapi->free(ctx->event_ctx); if ((dapi)&&(dapi->free)) dapi->free(ctx->dma_ctx); + for (dma = 0; dma < PCILIB_MAX_DMA_ENGINES; dma++) { + if (ctx->dma_rlock[dma]) + pcilib_return_lock(ctx, PCILIB_LOCK_FLAGS_DEFAULT, ctx->dma_rlock[dma]); + if (ctx->dma_wlock[dma]) + pcilib_return_lock(ctx, PCILIB_LOCK_FLAGS_DEFAULT, ctx->dma_wlock[dma]); + } + pcilib_free_register_banks(ctx); if (ctx->register_ctx) @@ -375,7 +399,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"); @@ -400,7 +427,7 @@ void pcilib_close(pcilib_t *ctx) { if (ctx->model) free(ctx->model); - + if (ctx->handle >= 0) close(ctx->handle); @@ -438,11 +465,16 @@ static int pcilib_update_pci_configuration_space(pcilib_t *ctx) { } size = read(ctx->pci_cfg_space_fd, ctx->pci_cfg_space_cache, 256); - if (size != 256) { - pcilib_error("Failed to read PCI configuration from sysfs"); + if (size < 64) { + if (size <= 0) + pcilib_error("Failed to read PCI configuration from sysfs, errno: %i", errno); + else + pcilib_error("Failed to read PCI configuration from sysfs, only %zu bytes read (expected at least 64)", size); return PCILIB_ERROR_FAILED; } + ctx->pci_cfg_space_size = size; + return 0; } @@ -465,7 +497,7 @@ static uint32_t *pcilib_get_pci_capabilities(pcilib_t *ctx, int cap_id) { cap = ctx->pci_cfg_space_cache[(0x34>>2)]; cap_offset = cap&0xFC; - while ((cap_offset)&&(cap_offset < 256)) { + while ((cap_offset)&&(cap_offset < ctx->pci_cfg_space_size)) { cap = ctx->pci_cfg_space_cache[cap_offset>>2]; if ((cap&0xFF) == cap_id) return &ctx->pci_cfg_space_cache[cap_offset>>2]; diff --git a/pcilib/pci.h b/pcilib/pci.h index d176caf..340abd3 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; @@ -41,6 +42,7 @@ struct pcilib_s { int pci_cfg_space_fd; /**< File descriptor linking to PCI configuration space in sysfs */ uint32_t pci_cfg_space_cache[64]; /**< Cached PCI configuration space */ + size_t pci_cfg_space_size; /**< Size of the cached PCI configuration space, sometimes not fully is available for unpriveledged user */ const uint32_t *pcie_capabilities; /**< PCI Capbility structure (just a pointer at appropriate place in the pci_cfg_space) */ int reg_bar_mapped; /**< Indicates that all BARs used to access registers are mapped */ @@ -70,6 +72,11 @@ struct pcilib_s { pcilib_dma_context_t *dma_ctx; /**< DMA context */ pcilib_context_t *event_ctx; /**< Implmentation context */ + pcilib_lock_t *dma_rlock[PCILIB_MAX_DMA_ENGINES]; /**< Per-engine locks to serialize streaming and read operations */ + pcilib_lock_t *dma_wlock[PCILIB_MAX_DMA_ENGINES]; /**< Per-engine locks to serialize write operations */ + + struct pcilib_locking_s locks; /**< Context of locking subsystem */ + #ifdef PCILIB_FILE_IO int file_io_handle; #endif /* PCILIB_FILE_IO */ diff --git a/pcilib/pcilib.h b/pcilib/pcilib.h index 8a43bfb..01f3324 100644 --- a/pcilib/pcilib.h +++ b/pcilib/pcilib.h @@ -63,6 +63,17 @@ typedef enum { } pcilib_dma_flags_t; typedef enum { + PCILIB_STREAMING_STOP = 0, /**< stop streaming */ + PCILIB_STREAMING_CONTINUE = 1, /**< wait the default DMA timeout for a new data */ + PCILIB_STREAMING_WAIT = 2, /**< wait the specified timeout for a new data */ + PCILIB_STREAMING_CHECK = 3, /**< do not wait for the data, bail out imideatly if no data ready */ + PCILIB_STREAMING_FAIL = 4, /**< fail if data is not available on timeout */ + PCILIB_STREAMING_REQ_FRAGMENT = 5, /**< only fragment of a packet is read, wait for next fragment and fail if no data during DMA timeout */ + PCILIB_STREAMING_REQ_PACKET = 6, /**< wait for next packet and fail if no data during the specified timeout */ + PCILIB_STREAMING_TIMEOUT_MASK = 3 /**< mask specifying all timeout modes */ +} pcilib_streaming_action_t; + +typedef enum { PCILIB_EVENT_FLAGS_DEFAULT = 0, PCILIB_EVENT_FLAG_RAW_DATA_ONLY = 1, /**< Do not parse data, just read raw and pass it to rawdata callback. If passed to rawdata callback, idicates the data is not identified as event (most probably just padding) */ PCILIB_EVENT_FLAG_STOP_ONLY = 1, /**< Do not cleanup, just stop acquiring new frames, the cleanup should be requested afterwards */ diff --git a/pcitool/CMakeLists.txt b/pcitool/CMakeLists.txt index 1b21041..4aea142 100644 --- a/pcitool/CMakeLists.txt +++ b/pcitool/CMakeLists.txt @@ -12,10 +12,10 @@ link_directories( if (NOT DISABLE_PCITOOL) add_executable(pci cli.c sysinfo.c formaters.c) set(HEADERS ${HEADERS} sysinfo.h formaters.h) - add_dependencies(pci pcitool) + add_dependencies(pci pcilib) target_link_libraries(pci pcilib ${FASTWRITER_LIBRARIES}) set_target_properties(pci PROPERTIES - LINK_FLAGS ${CMAKE_THREAD_LIBS_INIT} + LINK_FLAGS "${CMAKE_THREAD_LIBS_INIT} ${EXTRA_SYSTEM_LIBS}" ) #set_target_properties(pci PROPERTIES diff --git a/pcitool/cli.c b/pcitool/cli.c index d24e357..226ca55 100644 --- a/pcitool/cli.c +++ b/pcitool/cli.c @@ -1,3 +1,4 @@ +#define _XOPEN_SOURCE 700 #define _POSIX_C_SOURCE 200112L #define _BSD_SOURCE @@ -38,6 +39,7 @@ #include "debug.h" #include "model.h" #include "xml.h" +#include "locking.h" /* defines */ #define MAX_KBUF 14 @@ -91,7 +93,11 @@ typedef enum { MODE_LIST_KMEM, MODE_READ_KMEM, MODE_FREE_KMEM, - MODE_VALIDATE_XML + MODE_VALIDATE_XML + MODE_LIST_LOCKS, + MODE_FREE_LOCKS, + MODE_LOCK, + MODE_UNLOCK } MODE; typedef enum { @@ -163,6 +169,10 @@ typedef enum { OPT_LIST_KMEM, OPT_FREE_KMEM, OPT_READ_KMEM, + OPT_LIST_LOCKS, + OPT_FREE_LOCKS, + OPT_LOCK, + OPT_UNLOCK, OPT_BLOCK_SIZE, OPT_ALIGNMENT, OPT_TYPE, @@ -213,6 +223,10 @@ static struct option long_options[] = { {"read-kernel-memory", required_argument, 0, OPT_READ_KMEM }, {"alloc-kernel-memory", required_argument, 0, OPT_ALLOC_KMEM }, {"free-kernel-memory", required_argument, 0, OPT_FREE_KMEM }, + {"list-locks", no_argument, 0, OPT_LIST_LOCKS }, + {"free-locks", no_argument, 0, OPT_FREE_LOCKS }, + {"lock", required_argument, 0, OPT_LOCK }, + {"unlock", required_argument, 0, OPT_UNLOCK }, {"type", required_argument, 0, OPT_TYPE }, {"block-size", required_argument, 0, OPT_BLOCK_SIZE }, {"alignment", required_argument, 0, OPT_ALIGNMENT }, @@ -279,6 +293,11 @@ void Usage(int argc, char *argv[], const char *format, ...) { " dma - Remove all buffers allocated by DMA subsystem\n" " #number - Remove all buffers with the specified use id\n" "\n" +" --list-locks - List all registered locks\n" +" --free-locks - Destroy all locks (DANGEROUS)\n" +" --lock <lock name> - Obtain persistent lock\n" +" --unlock <lock name> - Release persistent lock\n" +"\n" " Addressing:\n" " -d <device> - FPGA device (/dev/fpga0)\n" " -m <model> - Memory model (autodetected)\n" @@ -1270,7 +1289,7 @@ typedef struct { int verbose; pcilib_timeout_t timeout; size_t run_time; - size_t trigger_time; + size_t trigger_time; size_t max_triggers; pcilib_event_flags_t flags; FORMAT format; @@ -1282,17 +1301,19 @@ typedef struct { volatile int run_flag; volatile int writing_flag; - struct timeval first_frame; + struct timeval first_frame; struct timeval last_frame; - size_t last_num; - + size_t last_num, last_id; + size_t trigger_failed; size_t trigger_count; - size_t event_count; - size_t incomplete_count; - size_t broken_count; - size_t missing_count; - size_t storage_count; + size_t event_count; /**< Total number of events (including bad ones, but excluding events expected, but not reported by hardware) */ + size_t incomplete_count; /**< Broken events, we even can't extract appropriate block of raw data */ + size_t broken_count; /**< Broken events, error while decoding in the requested format */ + size_t empty_count; /**< Broken events, no associated data or unknown */ + size_t missing_count; /**< Missing events, not received from the hardware */ + size_t dropped_count; /**< Missing events, dropped due slow decoding/copying performance */ + size_t storage_count; /**< Missing events, dropped due to slowness of the storage subsystem */ struct timeval start_time; struct timeval stop_time; @@ -1320,12 +1341,13 @@ int GrabCallback(pcilib_event_id_t event_id, pcilib_event_info_t *info, void *us ctx->missing_count += missing_count; #ifdef PCILIB_DEBUG_MISSING_EVENTS if (missing_count) - pcilib_debug(MISSING_EVENTS, "%zu missing events between %zu and %zu", missing_count, ctx->last_num, info->seqnum); + pcilib_debug(MISSING_EVENTS, "%zu missing events between %zu (hwid: %zu) and %zu (hwid: %zu)", missing_count, ctx->last_id, ctx->last_num, event_id, info->seqnum); #endif /* PCILIB_DEBUG_MISSING_EVENTS */ } ctx->last_num = info->seqnum; - + ctx->last_id = event_id; + if (info->flags&PCILIB_EVENT_INFO_FLAG_BROKEN) { ctx->incomplete_count++; return PCILIB_STREAMING_CONTINUE; @@ -1338,9 +1360,19 @@ int GrabCallback(pcilib_event_id_t event_id, pcilib_event_info_t *info, void *us default: data = pcilib_get_data(handle, event_id, PCILIB_EVENT_RAW_DATA, &size); } - + if (!data) { - ctx->broken_count++; + int err = (int)size; + switch (err) { + case PCILIB_ERROR_OVERWRITTEN: + ctx->dropped_count++; + break; + case PCILIB_ERROR_INVALID_DATA: + ctx->broken_count++; + break; + default: + ctx->empty_count++; + } return PCILIB_STREAMING_CONTINUE; } @@ -1373,7 +1405,7 @@ int GrabCallback(pcilib_event_id_t event_id, pcilib_event_info_t *info, void *us err = pcilib_return_data(handle, event_id, ctx->data, data); if (err) { - ctx->missing_count++; + ctx->dropped_count++; fastwriter_cancel(ctx->writer); return PCILIB_STREAMING_CONTINUE; } @@ -1485,7 +1517,7 @@ void GrabStats(GRABContext *ctx, struct timeval *end_time) { total = ctx->event_count + ctx->missing_count; } - good = ctx->event_count - ctx->broken_count - ctx->incomplete_count - ctx->storage_count; + good = ctx->event_count - ctx->broken_count - ctx->incomplete_count - ctx->storage_count - ctx->empty_count - ctx->dropped_count; if (ctx->event_count > 1) { fps = (ctx->event_count - 1) / (1.*fps_duration/1000000); @@ -1541,11 +1573,11 @@ void GrabStats(GRABContext *ctx, struct timeval *end_time) { printf("Good: "); PrintNumber(good); printf(", Dropped: "); - PrintNumber(ctx->storage_count); + PrintNumber(ctx->dropped_count + ctx->storage_count); printf(", Bad: "); - PrintNumber(ctx->incomplete_count); + PrintNumber(ctx->incomplete_count + ctx->broken_count); printf(", Empty: "); - PrintNumber(ctx->broken_count); + PrintNumber(ctx->empty_count); } printf(", Lost: "); PrintNumber(ctx->missing_count); @@ -1560,11 +1592,11 @@ void GrabStats(GRABContext *ctx, struct timeval *end_time) { printf("Good: "); PrintPercent(good, total); printf("%% Dropped: "); - PrintPercent(ctx->storage_count, total); + PrintPercent(ctx->dropped_count + ctx->storage_count, total); printf("%% Bad: "); - PrintPercent(ctx->incomplete_count, total); + PrintPercent(ctx->incomplete_count + ctx->broken_count, total); printf("%% Empty: "); - PrintPercent(ctx->broken_count, total); + PrintPercent(ctx->empty_count, total); } printf("%% Lost: "); @@ -1945,7 +1977,7 @@ size_t FindUse(size_t *n_uses, kmem_use_info_t *uses, pcilib_kmem_use_t use) { if (n == MAX_USES) return 0; - memset(&uses[n], 0, sizeof(pcilib_kmem_use_t)); + memset(&uses[n], 0, sizeof(kmem_use_info_t)); uses[n].use = use; return (*n_uses)++; } @@ -2035,6 +2067,8 @@ int ListKMEM(pcilib_t *handle, const char *device) { size_t i, useid, n_uses; kmem_use_info_t uses[MAX_USES]; + const pcilib_model_description_t *model_info = pcilib_get_model_description(handle); + err = ParseKMEM(handle, device, &n_uses, uses); if (err) Error("Failed to parse kernel memory information provided through sysfs"); @@ -2042,7 +2076,7 @@ int ListKMEM(pcilib_t *handle, const char *device) { printf("No kernel memory is allocated\n"); return 0; } - + printf("Use Type Count Total Size REF Mode \n"); printf("--------------------------------------------------------------------------------\n"); for (useid = 0; useid < n_uses; useid++) { @@ -2052,17 +2086,39 @@ int ListKMEM(pcilib_t *handle, const char *device) { } else i = useid + 1; printf("%08x ", uses[i].use); - if (!i) printf("All Others "); - else if ((uses[i].use >> 16) == PCILIB_KMEM_USE_DMA_RING) printf("DMA%u %s Ring ", uses[i].use&0x7F, ((uses[i].use&0x80)?"S2C":"C2S")); - else if ((uses[i].use >> 16) == PCILIB_KMEM_USE_DMA_PAGES) printf("DMA%u %s Pages ", uses[i].use&0x7F, ((uses[i].use&0x80)?"S2C":"C2S")); - else if ((uses[i].use >> 16) == PCILIB_KMEM_USE_USER) printf("User %04x ", uses[i].use&0xFFFF); - else printf (" "); + if (i) { + switch(PCILIB_KMEM_USE_TYPE(uses[i].use)) { + case PCILIB_KMEM_USE_DMA_RING: + printf("DMA%u %s Ring ", uses[i].use&0x7F, ((uses[i].use&0x80)?"S2C":"C2S")); + break; + case PCILIB_KMEM_USE_DMA_PAGES: + printf("DMA%u %s Pages ", uses[i].use&0x7F, ((uses[i].use&0x80)?"S2C":"C2S")); + break; + case PCILIB_KMEM_USE_SOFTWARE_REGISTERS: { + pcilib_register_bank_t bank = pcilib_find_register_bank_by_addr(handle, PCILIB_KMEM_USE_SUBTYPE(uses[i].use)); + if (bank == PCILIB_REGISTER_BANK_INVALID) + printf("SoftRegs (%8u)", PCILIB_KMEM_USE_SUBTYPE(uses[i].use)); + else + printf("SoftRegs (%8s)", model_info->banks[bank].name); + break; + } + case PCILIB_KMEM_USE_LOCKS: + printf("Locks "); + break; + case PCILIB_KMEM_USE_USER: + printf("User %04x ", uses[i].use&0xFFFF); + break; + default: + printf (" "); + } + } else printf("All Others "); + printf(" "); printf("%6zu", uses[i].count); printf(" "); printf("%10s", GetPrintSize(stmp, uses[i].size)); printf(" "); - if (uses[i].referenced&&uses[i].hw_lock) printf("HW+SW"); + if ((uses[i].referenced)&&(uses[i].hw_lock)) printf("HW+SW"); else if (uses[i].referenced) printf(" SW"); else if (uses[i].hw_lock) printf("HW "); else printf(" - "); @@ -2402,6 +2458,88 @@ int ReadBuffer(pcilib_t *handle, const char *device, const pcilib_model_descript return ReadKMEM(handle, device, ((dma&0x7F)|((dma_direction == PCILIB_DMA_TO_DEVICE)?0x80:0x00))|(PCILIB_KMEM_USE_DMA_PAGES<<16), block, size, o); } +int ListLocks(pcilib_t *ctx, int verbose) { + int err; + pcilib_lock_id_t i; + + if (verbose) + printf("ID Refs Flags Locked Name\n"); + else + printf("ID Refs Flags Name\n"); + printf("--------------------------------------------------------------------------------\n"); + + 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_lock_flags_t flags = pcilib_lock_get_flags(lock); + size_t refs = pcilib_lock_get_refs(lock); + + printf("%4u %4zu ", i, refs); + + if (flags&PCILIB_LOCK_FLAG_PERSISTENT) printf("P"); + else printf(" "); + printf(" "); + + if (verbose) { + err = pcilib_lock_custom(lock, PCILIB_LOCK_FLAGS_DEFAULT, PCILIB_TIMEOUT_IMMEDIATE); + switch (err) { + case 0: + pcilib_unlock(lock); + printf("No "); + break; + case PCILIB_ERROR_TIMEOUT: + printf("Yes "); + break; + default: + printf("Err: %3i ", err); + } + } + printf("%s\n", name); + } + printf("--------------------------------------------------------------------------------\n"); + printf("P - Persistent\n"); + + return 0; +} + +int FreeLocks(pcilib_t *handle, int force) { + return pcilib_destroy_all_locks(handle, force); +} + +int LockUnlock(pcilib_t *handle, const char *name, int do_lock, pcilib_timeout_t timeout) { + int err = 0; + + pcilib_lock_t *lock = pcilib_get_lock(handle, PCILIB_LOCK_FLAG_PERSISTENT, name); + if (!lock) Error("Error getting persistent lock %s", name); + + if (do_lock) + err = pcilib_lock_custom(lock, PCILIB_LOCK_FLAGS_DEFAULT, timeout); + else + pcilib_unlock(lock); + + if (err) { + pcilib_return_lock(handle, PCILIB_LOCK_FLAGS_DEFAULT, lock); + switch (err) { + case PCILIB_ERROR_TIMEOUT: + printf("Timeout locking %s\n", name); + break; + default: + Error("Error (%i) locking %s", err, name); + } + } else if (do_lock) { + pcilib_lock_ref(lock); + pcilib_return_lock(handle, PCILIB_LOCK_FLAGS_DEFAULT, lock); + printf("%s is locked\n", name); + } else { + pcilib_lock_unref(lock); + pcilib_return_lock(handle, PCILIB_LOCK_FLAGS_DEFAULT, lock); + } + + return err; +} + int EnableIRQ(pcilib_t *handle, const pcilib_model_description_t *model_info, pcilib_irq_type_t irq_type) { int err; @@ -2489,6 +2627,7 @@ int main(int argc, char **argv) { const char *data_type = NULL; const char *dma_channel = NULL; const char *use = NULL; + const char *lock = NULL; size_t block = (size_t)-1; pcilib_irq_type_t irq_type = PCILIB_IRQ_TYPE_ALL; pcilib_irq_hw_source_t irq_source = PCILIB_IRQ_SOURCE_DEFAULT; @@ -2695,7 +2834,6 @@ int main(int argc, char **argv) { case OPT_LIST_KMEM: if (mode != MODE_INVALID) Usage(argc, argv, "Multiple operations are not supported"); mode = MODE_LIST_KMEM; - if (!model) model = "pci"; if (optarg) use = optarg; else if ((optind < argc)&&(argv[optind][0] != '-')) use = argv[optind++]; @@ -2745,6 +2883,25 @@ int main(int argc, char **argv) { if (optarg) use = optarg; else if ((optind < argc)&&(argv[optind][0] != '-')) use = argv[optind++]; break; + case OPT_LIST_LOCKS: + if (mode != MODE_INVALID) Usage(argc, argv, "Multiple operations are not supported"); + mode = MODE_LIST_LOCKS; + break; + case OPT_FREE_LOCKS: + if (mode != MODE_INVALID) Usage(argc, argv, "Multiple operations are not supported"); + mode = MODE_FREE_LOCKS; + model = "maintenance"; + break; + case OPT_LOCK: + if (mode != MODE_INVALID) Usage(argc, argv, "Multiple operations are not supported"); + mode = MODE_LOCK; + lock = optarg; + break; + case OPT_UNLOCK: + if (mode != MODE_INVALID) Usage(argc, argv, "Multiple operations are not supported"); + mode = MODE_UNLOCK; + lock = optarg; + break; case OPT_DEVICE: fpga_device = optarg; break; @@ -2955,7 +3112,6 @@ int main(int argc, char **argv) { model_info = pcilib_get_model_description(handle); dma_info = pcilib_get_dma_description(handle); - switch (mode) { case MODE_WRITE: if (((argc - optind) == 1)&&(*argv[optind] == '*')) { @@ -3223,6 +3379,18 @@ int main(int argc, char **argv) { case MODE_FREE_KMEM: FreeKMEM(handle, fpga_device, use, force); break; + case MODE_LIST_LOCKS: + ListLocks(handle, verbose); + break; + case MODE_FREE_LOCKS: + FreeLocks(handle, force); + break; + case MODE_LOCK: + LockUnlock(handle, lock, 1, timeout_set?timeout:PCILIB_TIMEOUT_INFINITE); + break; + case MODE_UNLOCK: + LockUnlock(handle, lock, 0, timeout_set?timeout:PCILIB_TIMEOUT_INFINITE); + break; case MODE_INVALID: break; } diff --git a/protocols/software.c b/protocols/software.c index 5534dc7..55f62e8 100644 --- a/protocols/software.c +++ b/protocols/software.c @@ -13,10 +13,10 @@ typedef struct pcilib_software_register_bank_context_s pcilib_software_register_bank_context_t; struct pcilib_software_register_bank_context_s { - pcilib_register_bank_context_t bank_ctx; + pcilib_register_bank_context_t bank_ctx; /**< the bank context associated with the software registers */ - pcilib_kmem_handle_t *kmem; - void *addr; + pcilib_kmem_handle_t *kmem; /**< the kernel memory for software registers */ + void *addr; /**< the virtual adress of the allocated kernel memory*/ }; /** @@ -40,8 +40,10 @@ void pcilib_software_registers_close(pcilib_t *ctx, pcilib_register_bank_context * @return a bank context with the adress of the kernel space memory related to it */ pcilib_register_bank_context_t* pcilib_software_registers_open(pcilib_t *ctx, pcilib_register_bank_t bank, const char* model, const void *args) { + int err; pcilib_software_register_bank_context_t *bank_ctx; pcilib_kmem_handle_t *handle; + pcilib_lock_t *lock; pcilib_kmem_reuse_state_t reused; const pcilib_register_bank_description_t *bank_desc = ctx->banks + bank; @@ -52,11 +54,33 @@ pcilib_register_bank_context_t* pcilib_software_registers_open(pcilib_t *ctx, pc } bank_ctx = calloc(1, sizeof(pcilib_software_register_bank_context_t)); + if (!bank_ctx) { + pcilib_error("Memory allocation for bank context has failed"); + return NULL; + } + + lock = pcilib_get_lock(ctx, PCILIB_LOCK_FLAGS_DEFAULT, "softreg/%s", bank_desc->name); + if (!lock) { + pcilib_software_registers_close(ctx, (pcilib_register_bank_context_t*)bank_ctx); + pcilib_error("Failed to initialize a lock to protect bank %s with software registers", bank_desc->name); + return NULL; + } + + err = pcilib_lock(lock); + if (err) { + pcilib_return_lock(ctx, PCILIB_LOCK_FLAGS_DEFAULT, lock); + pcilib_software_registers_close(ctx, (pcilib_register_bank_context_t*)bank_ctx); + pcilib_error("Error (%i) obtaining lock on bank %s with software registers", err, bank_desc->name); + return NULL; + } + - handle = pcilib_alloc_kernel_memory(ctx, PCILIB_KMEM_TYPE_PAGE, 1, PCILIB_KMEM_PAGE_SIZE, 0, PCILIB_KMEM_USE(PCILIB_KMEM_USE_SOFTWARE_REGISTERS, bank), PCILIB_KMEM_FLAG_REUSE|PCILIB_KMEM_FLAG_PERSISTENT); + handle = pcilib_alloc_kernel_memory(ctx, PCILIB_KMEM_TYPE_PAGE, 1, PCILIB_KMEM_PAGE_SIZE, 0, PCILIB_KMEM_USE(PCILIB_KMEM_USE_SOFTWARE_REGISTERS, bank_desc->addr), PCILIB_KMEM_FLAG_REUSE|PCILIB_KMEM_FLAG_PERSISTENT); if (!handle) { - pcilib_error("Allocation of kernel memory for software registers has failed"); + pcilib_unlock(lock); + pcilib_return_lock(ctx, PCILIB_LOCK_FLAGS_DEFAULT, lock); pcilib_software_registers_close(ctx, (pcilib_register_bank_context_t*)bank_ctx); + pcilib_error("Allocation of kernel memory for software registers has failed"); return NULL; } @@ -68,8 +92,10 @@ pcilib_register_bank_context_t* pcilib_software_registers_open(pcilib_t *ctx, pc pcilib_register_t i; if (reused & PCILIB_KMEM_REUSE_PARTIAL) { - pcilib_error("Inconsistent software registers are found (only part of required buffers is available)"); + pcilib_unlock(lock); + pcilib_return_lock(ctx, PCILIB_LOCK_FLAGS_DEFAULT, lock); pcilib_software_registers_close(ctx, (pcilib_register_bank_context_t*)bank_ctx); + pcilib_error("Inconsistent software registers are found (only part of required buffers is available)"); return NULL; } @@ -80,6 +106,9 @@ pcilib_register_bank_context_t* pcilib_software_registers_open(pcilib_t *ctx, pc } } + pcilib_unlock(lock); + pcilib_return_lock(ctx, PCILIB_LOCK_FLAGS_DEFAULT, lock); + return (pcilib_register_bank_context_t*)bank_ctx; } @@ -97,7 +126,8 @@ int pcilib_software_registers_read(pcilib_t *ctx, pcilib_register_bank_context_t pcilib_error("Trying to access space outside of the define register bank (bank: %s, addr: 0x%lx)", bank_ctx->bank->name, addr); return PCILIB_ERROR_INVALID_ADDRESS; } - + + // we consider this atomic operation and, therefore, do no locking *value = *(pcilib_register_value_t*)(((pcilib_software_register_bank_context_t*)bank_ctx)->addr + addr); return 0; } @@ -111,12 +141,13 @@ int pcilib_software_registers_read(pcilib_t *ctx, pcilib_register_bank_context_t * @param[out] value the value we want to write in the register * @return 0 in case of success */ -int pcilib_software_registers_write(pcilib_t *ctx, pcilib_register_bank_context_t *bank_ctx, pcilib_register_addr_t addr, pcilib_register_value_t value){ +int pcilib_software_registers_write(pcilib_t *ctx, pcilib_register_bank_context_t *bank_ctx, pcilib_register_addr_t addr, pcilib_register_value_t value) { if ((addr + sizeof(pcilib_register_value_t)) > bank_ctx->bank->size) { pcilib_error("Trying to access space outside of the define register bank (bank: %s, addr: 0x%lx)", bank_ctx->bank->name, addr); return PCILIB_ERROR_INVALID_ADDRESS; } + // we consider this atomic operation and, therefore, do no locking *(pcilib_register_value_t*)(((pcilib_software_register_bank_context_t*)bank_ctx)->addr + addr) = value; return 0; } |