diff options
author | Suren A. Chilingaryan <csa@suren.me> | 2023-05-25 22:41:04 +0200 |
---|---|---|
committer | Suren A. Chilingaryan <csa@suren.me> | 2023-05-25 22:41:04 +0200 |
commit | 6f4af841f6fdd099b97d071ae64c8be60f809456 (patch) | |
tree | d4f9a18b38e1ce3cfc0a5336215d5ce3afe830d2 /events.c | |
download | pcidev-6f4af841f6fdd099b97d071ae64c8be60f809456.tar.gz pcidev-6f4af841f6fdd099b97d071ae64c8be60f809456.tar.bz2 pcidev-6f4af841f6fdd099b97d071ae64c8be60f809456.tar.xz pcidev-6f4af841f6fdd099b97d071ae64c8be60f809456.zip |
A sample event engine for pcitool (not requiring any PCIe hardware). Initial (barely tested and intended only as an example) release
Diffstat (limited to 'events.c')
-rw-r--r-- | events.c | 592 |
1 files changed, 592 insertions, 0 deletions
diff --git a/events.c b/events.c new file mode 100644 index 0000000..1500a24 --- /dev/null +++ b/events.c @@ -0,0 +1,592 @@ +#define _PCIDEV_IMAGE_C +#define _DEFAULT_SOURCE +#define _BSD_SOURCE +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/time.h> +#include <pthread.h> +#include <assert.h> + +#include <pcilib.h> +#include <pcilib/tools.h> +#include <pcilib/error.h> +#include <pcilib/event.h> +#include <pcilib/cpu.h> +#include <pcilib/timing.h> + +#include "private.h" +#include "model.h" +#include "events.h" + + +#define FIND_REG(var, bank, name) \ + ctx->var = pcilib_find_register(pcilib, bank, name); \ + if (ctx->var == PCILIB_REGISTER_INVALID) { \ + err = PCILIB_ERROR_NOTFOUND; \ + pcilib_error("Unable to find a %s register", name); \ + } + + +#define GET_REG(reg, var) \ + if (!err) { \ + err = pcilib_read_register_by_id(pcilib, ctx->reg, &var); \ + if (err) { \ + pcilib_error("Error reading %s register", model_info->registers[ctx->reg].name); \ + } \ + } + +#define SET_REG(reg, val) \ + if (!err) { \ + err = pcilib_write_register_by_id(pcilib, ctx->reg, val); \ + if (err) { \ + pcilib_error("Error writting %s register", model_info->registers[ctx->reg].name); \ + } \ + } + +#define CHECK_REG(reg, check) \ + if (!err) { \ + err = pcilib_read_register_by_id(pcilib, ctx->reg, &value); \ + if (err) { \ + pcilib_error("Error reading %s register", model_info->registers[ctx->reg].name); \ + } \ + if (value != check) { \ + pcilib_error("Unexpected value (0x%lx) of register %s", value, model_info->registers[ctx->reg].name); \ + err = PCILIB_ERROR_INVALID_DATA; \ + } \ + } + +#define CHECK_STATUS() + //CHECK_REG(status_reg, PCIDEV_GET_EXPECTED_STATUS(ctx)) + +#define CHECK_VALUE(value, val) \ + if ((!err)&&(value != val)) { \ + pcilib_error("Unexpected value (0x%x) in data stream (0x%x is expected)", value, val); \ + err = PCILIB_ERROR_INVALID_DATA; \ + } + +#define CHECK_FLAG(flag, check, ...) \ + if ((!err)&&(!(check))) { \ + pcilib_error("Unexpected value (0x%x) of " flag, __VA_ARGS__); \ + err = PCILIB_ERROR_INVALID_DATA; \ + } + +#define LOCK(lock_name) \ + err = pcilib_try_lock(ctx->lock_name##_lock); \ + if (err) { \ + pcilib_error("IPECamera is busy"); \ + return PCILIB_ERROR_BUSY; \ + } \ + ctx->lock_name##_locked = 1; + +#define UNLOCK(lock_name) \ + if (ctx->lock_name##_locked) { \ + pcilib_unlock(ctx->lock_name##_lock); \ + ctx->lock_name##_locked = 0; \ + } + + +pcilib_context_t *pcidev_init(pcilib_t *pcilib) { + int err = 0; + + pcidev_t *ctx = malloc(sizeof(pcidev_t)); + + if (ctx) { + memset(ctx, 0, sizeof(pcidev_t)); + + ctx->run_lock = pcilib_get_lock(pcilib, PCILIB_LOCK_FLAGS_DEFAULT, "pcidev"); + ctx->stream_lock = pcilib_get_lock(pcilib, PCILIB_LOCK_FLAGS_DEFAULT, "pcidev/stream"); + ctx->trigger_lock = pcilib_get_lock(pcilib, PCILIB_LOCK_FLAGS_DEFAULT, "pcidev/trigger"); + + if (!ctx->run_lock||!ctx->stream_lock||!ctx->trigger_lock) { + free(ctx); + pcilib_error("Failed to initialize locks to protect pcidev operation"); + return NULL; + } + + ctx->buffer_size = PCIDEV_DEFAULT_BUFFER_SIZE; + ctx->rdma = PCILIB_DMA_ENGINE_INVALID; + + if (err) { + free(ctx); + return NULL; + } + } + + return (pcilib_context_t*)ctx; +} + +void pcidev_free(pcilib_context_t *vctx) { + if (vctx) { + pcidev_t *ctx = (pcidev_t*)vctx; + pcidev_stop(vctx, PCILIB_EVENT_FLAGS_DEFAULT); + + if (ctx->trigger_lock) + pcilib_return_lock(vctx->pcilib, PCILIB_LOCK_FLAGS_DEFAULT, ctx->trigger_lock); + + if (ctx->stream_lock) + pcilib_return_lock(vctx->pcilib, PCILIB_LOCK_FLAGS_DEFAULT, ctx->stream_lock); + + if (ctx->run_lock) + pcilib_return_lock(vctx->pcilib, PCILIB_LOCK_FLAGS_DEFAULT, ctx->run_lock); + + free(ctx); + } +} + +pcilib_dma_context_t *pcidev_init_dma(pcilib_context_t *vctx) { + const pcilib_model_description_t *model_info = pcilib_get_model_description(vctx->pcilib); + if ((!model_info->dma)||(!model_info->dma->api)||(!model_info->dma->api->init)) { + pcilib_error("The DMA engine is not configured in model"); + return NULL; + } + + return model_info->dma->api->init(vctx->pcilib, "pcidev", NULL); +} + + +int pcidev_set_buffer_size(pcidev_t *ctx, int size) { + if (ctx->started) { + pcilib_error("Can't change buffer size while grabbing"); + return PCILIB_ERROR_INVALID_REQUEST; + } + + if (size < 2) { + pcilib_error("The buffer size is too small"); + return PCILIB_ERROR_INVALID_REQUEST; + } + + if ((size^(size-1)) < size) { + pcilib_error("The buffer size is not power of 2"); + } + + ctx->buffer_size = size; + + return 0; +} + +int pcidev_start(pcilib_context_t *vctx, pcilib_event_t event_mask, pcilib_event_flags_t flags) { + int i; + int err = 0; + + pcidev_t *ctx = (pcidev_t*)vctx; +// pcilib_register_value_t value; + + if (!ctx) { + pcilib_error("pcidev event engine is not initialized"); + return PCILIB_ERROR_NOTINITIALIZED; + } + + if (ctx->started) { + pcilib_error("pcidev event grabbing is already started"); + return PCILIB_ERROR_INVALID_REQUEST; + } + + LOCK(run); + + pcidev_debug(API, "pcidev: starting"); + + ctx->event_id = 0; + ctx->buffer_pos = 0; + ctx->process_data = (flags&PCILIB_EVENT_FLAG_RAW_DATA_ONLY)?0:1; + ctx->event_size = PCIDEV_EVENT_SIZE * sizeof(size_t); + + memset(&ctx->eio_timestamp, 0, sizeof(struct timeval)); + + ctx->buffer = malloc(ctx->event_size * ctx->buffer_size); + if (!ctx->buffer) { + pcidev_stop(vctx, PCILIB_EVENT_FLAGS_DEFAULT); + pcilib_error("Unable to allocate ring buffer (%lu bytes)", ctx->event_size * ctx->buffer_size); + return PCILIB_ERROR_MEMORY; + } + + ctx->event = (pcidev_event_t*)malloc(ctx->buffer_size * sizeof(pcidev_event_t)); + if (!ctx->event) { + pcidev_stop(vctx, PCILIB_EVENT_FLAGS_DEFAULT); + pcilib_error("Unable to allocate event-info buffer"); + return PCILIB_ERROR_MEMORY; + } + + memset(ctx->event, 0, ctx->buffer_size * sizeof(pcidev_event_t)); + + for (i = 0; i < ctx->buffer_size; i++) { + err = pthread_rwlock_init(&ctx->event[i].mutex, NULL); + if (err) break; + } + + ctx->event_mutex_destroy = i; + + if (!err) { + ctx->rdma = pcilib_find_dma_by_addr(vctx->pcilib, PCILIB_DMA_FROM_DEVICE, PCIDEV_DMA_ADDRESS); + if (ctx->rdma == PCILIB_DMA_ENGINE_INVALID) { + err = PCILIB_ERROR_NOTFOUND; + pcilib_error("The C2S channel of DMA Engine (%u) is not found", PCIDEV_DMA_ADDRESS); + } else { + err = pcilib_start_dma(vctx->pcilib, ctx->rdma, PCILIB_DMA_FLAGS_DEFAULT); + if (err) { + ctx->rdma = PCILIB_DMA_ENGINE_INVALID; + pcilib_error("Failed to initialize C2S channel of DMA Engine (%u)", PCIDEV_DMA_ADDRESS); + } + } + } + + if (err) { + pcidev_stop(vctx, PCILIB_EVENT_FLAGS_DEFAULT); + return err; + } + + if (vctx->params.autostop.duration) { + gettimeofday(&ctx->autostop.timestamp, NULL); + ctx->autostop.timestamp.tv_usec += vctx->params.autostop.duration % 1000000; + if (ctx->autostop.timestamp.tv_usec > 999999) { + ctx->autostop.timestamp.tv_sec += 1 + vctx->params.autostop.duration / 1000000; + ctx->autostop.timestamp.tv_usec -= 1000000; + } else { + ctx->autostop.timestamp.tv_sec += vctx->params.autostop.duration / 1000000; + } + } + + if (vctx->params.autostop.max_events) { + ctx->autostop.evid = vctx->params.autostop.max_events; + } + + ctx->started = 1; + + // Technically here we need to start DAQ and processing threads. DAQ thread will pull raw data from DMA and distribute it between processing threads... + // Current example doesn't spawn threads, but expects that enough buffering is performed by hardware and we can just allow user pull the data directly from hardware... + // So, here pcidev_stream combines functions of 3 threads: DAQ thread pulling and buffering raw data, processing threads generating event data, and stream function feading processed event data to user callback + + pcidev_debug(API, "pcidev: started"); + + return 0; +} + + +int pcidev_stop(pcilib_context_t *vctx, pcilib_event_flags_t flags) { + int i; + pcidev_t *ctx = (pcidev_t*)vctx; + + if (!ctx) { + pcilib_error("pcidev event engine is not initialized"); + return PCILIB_ERROR_NOTINITIALIZED; + } + + ctx->run_streamer = 0; + if (flags&PCILIB_EVENT_FLAG_STOP_ONLY) return 0; + + pcidev_debug(API, "pcidev: stopping"); + + if (ctx->started) { + // Here we would also normally wait untill all spawned threads are terminated + + while (ctx->streaming) { + usleep(PCIDEV_NOEVENT_SLEEP); + } + } + + if (ctx->event_mutex_destroy) { + for (i = 0; i < ctx->event_mutex_destroy; i++) { + pthread_rwlock_destroy(&ctx->event[i].mutex); + } + ctx->event_mutex_destroy = 0; + } + + if (ctx->rdma != PCILIB_DMA_ENGINE_INVALID) { + pcilib_stop_dma(vctx->pcilib, ctx->rdma, PCILIB_DMA_FLAGS_DEFAULT); + ctx->rdma = PCILIB_DMA_ENGINE_INVALID; + } + + + if (ctx->event) { + free(ctx->event); + ctx->event = NULL; + } + + if (ctx->buffer) { + free(ctx->buffer); + ctx->buffer = NULL; + } + + memset(&ctx->autostop, 0, sizeof(pcidev_autostop_t)); + memset(&ctx->eio_timestamp, 0, sizeof(struct timeval)); + + ctx->event_id = 0; + ctx->buffer_pos = 0; + ctx->started = 0; + + pcidev_debug(API, "pcidev: stopped"); + UNLOCK(run); + + return 0; +} + +int pcidev_reset(pcilib_context_t *vctx) { + pcidev_t *ctx = (pcidev_t*)vctx; + + if (!ctx) { + pcilib_error("pcidev event engine is not initialized"); + return PCILIB_ERROR_NOTINITIALIZED; + } + + // Technically we need to stop event generation, reset device, drop all pending data on DMA channels, restart device + + pcidev_debug(API, "pcidev: reset done"); + return 0; +} + + +int pcidev_trigger(pcilib_context_t *vctx, pcilib_event_t event, size_t trigger_size, void *trigger_data) { + return PCILIB_ERROR_NOTSUPPORTED; +} + + +typedef struct { + pcidev_t *pcidev; + pcilib_event_callback_t event_cb; + void *event_cb_ctx; +} pcidev_data_callback_user_t; + +static int pcidev_data_callback(void *user, pcilib_dma_flags_t flags, size_t bufsize, void *buf) { + int i; + int res; + int eoi = 0; //**< end-of-integration flag */ + + struct timeval packet_timestamp; //**< hardware timestamp of the packet (set by FPGA) */ + static unsigned long packet_id = 0; + + pcidev_data_callback_user_t *datacb_ctx = (pcidev_data_callback_user_t*)user; + + pcidev_t *ctx = datacb_ctx->pcidev; + pcilib_event_callback_t event_callback = datacb_ctx->event_cb; + void *event_callback_ctx = datacb_ctx->event_cb_ctx; + + packet_id++; + pcidev_debug_buffer(RAW_PACKETS, bufsize, buf, PCILIB_DEBUG_BUFFER_MKDIR, "event%4lu/packet%9lu", ctx->event_id, packet_id); + + // Packet analysis may come here + // This sample illustrates time-based events (e.g. histograms). Instead, we also can have size based events when we receive data until expected size is reached (e.g. from camera) + // We can check packet consistency here (e.g. verify magic number, packet seq number, and check CRC/MD5), return negative error code on the error + // If we deal with multi-packet events and started to receive data not from the beginning of the event, here we would skip data until new event begins by returning PCILIB_STREAMING_CONTINUE + // If we deal with multi-packet events and the event data is not yet complete, we would buffer it and return PCILIB_STREAMING_REQ_FRAGMENT + + + // We need to get the actual packet time-stamp here. The demo uses host timestamp instead + gettimeofday(&packet_timestamp, NULL); + + // Check if integration time is over. + if (pcilib_timecmp(&packet_timestamp, &ctx->eio_timestamp) > 0) { + // initialize timing on a first packet + if (ctx->eio_timestamp.tv_sec == 0) { + gettimeofday(&ctx->eio_timestamp, NULL); + pcilib_add_timeout(&ctx->eio_timestamp, PCIDEV_INTEGRATION_PERIOD); + } else + eoi = 1; + } + + if ((!ctx->run_streamer) + ||((ctx->event_id == ctx->autostop.evid)&&(ctx->event_id)) + ||(pcilib_check_deadline(&ctx->autostop.timestamp, 0)) + ) { + ctx->run_streamer = 0; + return PCILIB_STREAMING_STOP; + } + + if (eoi) { + pcidev_event_info_t info; + + ctx->event[ctx->buffer_pos].event.info.type = PCILIB_EVENT0; + ctx->event[ctx->buffer_pos].event.info.flags = 0; + + memcpy(&info, ctx->event + ctx->buffer_pos, sizeof(pcidev_event_info_t)); + + if (event_callback) { + res = event_callback(ctx->event_id + 1, (pcilib_event_info_t*)&info, event_callback_ctx); + if (res <= 0) { + ctx->run_streamer = 0; + if (res < 0) return -res; + return PCILIB_STREAMING_STOP; + } + } + + + ctx->buffer_pos = (++ctx->event_id) % ctx->buffer_size; + + // Compute next period. We don't handle case when the processing is not fast enough and we get behind... We should in real driver, it often happens. + pcilib_add_timeout(&ctx->eio_timestamp, PCIDEV_INTEGRATION_PERIOD); + } + + if (ctx->process_data) { + if (eoi) { + memset(ctx->buffer + ctx->buffer_pos * ctx->event_size, 0, ctx->event_size); + } + + for (i = 0; i < bufsize; i++) { + ((size_t*)ctx->buffer)[((uint8_t*)buf)[i]]++; + } + } + + if (ctx->pcictx.params.rawdata.callback) { + res = ctx->pcictx.params.rawdata.callback(ctx->event_id, (pcilib_event_info_t*)(ctx->event + ctx->buffer_pos), (eoi?PCILIB_EVENT_FLAG_EOF:PCILIB_EVENT_FLAGS_DEFAULT), bufsize, buf, ctx->pcictx.params.rawdata.user); + if (res <= 0) { + if (res < 0) return res; + return PCILIB_STREAMING_STOP; + } + } + + return PCILIB_STREAMING_REQ_FRAGMENT; +} + + + +int pcidev_stream(pcilib_context_t *vctx, pcilib_event_callback_t callback, void *user) { + int err = 0; + int do_stop = 0; + + pcidev_t *ctx = (pcidev_t*)vctx; + + if (!ctx) { + pcilib_error("pcidev event engine is not initialized"); + return PCILIB_ERROR_NOTINITIALIZED; + } + + pcidev_debug(API, "pcidev: start streaming"); + + pcidev_data_callback_user_t datacb_ctx; + datacb_ctx.pcidev = ctx; + datacb_ctx.event_cb = callback; + datacb_ctx.event_cb_ctx = user; + + LOCK(stream); + + ctx->streaming = 1; + ctx->run_streamer = 1; + + if (!ctx->started) { + err = pcidev_start(vctx, PCILIB_EVENTS_ALL, PCILIB_EVENT_FLAGS_DEFAULT); + if (err) { + ctx->streaming = 0; + return err; + } + + do_stop = 1; + } + + while (ctx->run_streamer) { + err = pcilib_stream_dma(ctx->pcictx.pcilib, ctx->rdma, 0, 0, PCILIB_DMA_FLAG_MULTIPACKET, PCIDEV_DMA_TIMEOUT, &pcidev_data_callback, &datacb_ctx); + if (err) { + if (err == PCILIB_ERROR_TIMEOUT) { + if (pcilib_check_deadline(&ctx->autostop.timestamp, 0)) { + ctx->run_streamer = 0; + break; + } + usleep(PCIDEV_NOEVENT_SLEEP); + } else pcilib_error("DMA error while reading pcidev events, error: %i", err); + } + } + + ctx->run_streamer = 0; + ctx->streaming = 0; + + UNLOCK(stream); + + pcidev_debug(API, "pcidev: streaming finished"); + + if (do_stop) { + pcidev_stop(vctx, PCILIB_EVENT_FLAGS_DEFAULT); + } + + return err; +} + +static int pcidev_resolve_event_id(pcidev_t *ctx, pcilib_event_id_t evid) { + pcilib_event_id_t diff; + + if (evid > ctx->event_id) { + diff = (((pcilib_event_id_t)-1) - ctx->event_id) + evid; + if (diff >= ctx->buffer_size) return -1; + } else { + diff = ctx->event_id - evid; + if (diff >= ctx->buffer_size) return -1; + } + + return (evid - 1) % ctx->buffer_size; +} + +int pcidev_get_data(pcilib_context_t *vctx, pcilib_event_id_t event_id, pcilib_event_data_type_t data_type, size_t arg_size, void *arg, size_t *size, void **ret) { + int buf_ptr; + pcidev_t *ctx = (pcidev_t*)vctx; + + void *data = *ret; + + if (!ctx) { + pcilib_error("pcidev event engine is not initialized"); + return PCILIB_ERROR_NOTINITIALIZED; + } + + pcidev_debug(API, "pcidev: get_data"); + + buf_ptr = pcidev_resolve_event_id(ctx, event_id); + if (buf_ptr < 0) { + pcidev_debug(HARDWARE, "The data of the requested event %zu has been meanwhile overwritten", event_id); + return PCILIB_ERROR_OVERWRITTEN; + } + + switch ((pcidev_data_type_t)data_type) { + case PCIDEV_RAW_DATA: + pcilib_error("The raw data is not buffered for the performance reasons and can only be accessed via rawdata-callback mechanism"); + return PCILIB_ERROR_NOTSUPPORTED; + case PCIDEV_STANDARD_DATA: + // We will lock the data for non-raw data to prevent ocasional overwritting. + pthread_rwlock_rdlock(&ctx->event[buf_ptr].mutex); + + // Check if data is still not overwritten + if (pcidev_resolve_event_id(ctx, event_id) < 0) { + pthread_rwlock_unlock(&ctx->event[buf_ptr].mutex); + pcidev_debug(HARDWARE, "The data of the requested event %zu has been meanwhile overwritten", event_id); + return PCILIB_ERROR_OVERWRITTEN; + } + + if (data) { + if ((!size)||(*size < ctx->event_size)) { + pthread_rwlock_unlock(&ctx->event[buf_ptr].mutex); + pcilib_warning("The size of event data is too big (%zu bytes) for user supplied buffer (%zu bytes)", ctx->event_size, (size?*size:0)); + return PCILIB_ERROR_TOOBIG; + } + memcpy(data, ctx->buffer + buf_ptr * ctx->event_size, ctx->event_size); + pthread_rwlock_unlock(&ctx->event[buf_ptr].mutex); + *size = ctx->event_size; + return 0; + } + + if (size) *size = ctx->event_size; + *ret = ctx->buffer + buf_ptr * ctx->event_size; + return 0; + default: + pcilib_error("Unknown data type (%li) is requested", data_type); + return PCILIB_ERROR_INVALID_REQUEST; + } +} + +int pcidev_return_data(pcilib_context_t *vctx, pcilib_event_id_t event_id, pcilib_event_data_type_t data_type, void *data) { + pcidev_t *ctx = (pcidev_t*)vctx; + + if (!ctx) { + pcilib_error("pcidev event engine is not initialized"); + return PCILIB_ERROR_NOTINITIALIZED; + + } + + if ((pcidev_data_type_t)data_type == PCIDEV_RAW_DATA) { + return PCILIB_ERROR_NOTSUPPORTED; + } else { + int buf_ptr = (event_id - 1) % ctx->buffer_size; + pthread_rwlock_unlock(&ctx->event[buf_ptr].mutex); + } + + pcidev_debug(API, "pcidev: return_data"); + + return 0; +} |