1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00
VILLASnode/lib/nodes/comedi.cpp

998 lines
28 KiB
C++
Raw Permalink Normal View History

2018-03-28 19:07:00 +02:00
/** Node type: comedi
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @author Daniel Krebs <github@daniel-krebs.net>
2020-01-20 17:17:00 +01:00
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
2018-03-28 19:07:00 +02:00
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <cstring>
#include <cmath>
#include <sys/mman.h>
#include <comedilib.h>
#include <comedi_errno.h>
2018-03-28 19:07:00 +02:00
2021-06-21 16:11:42 -04:00
#include <villas/node.h>
#include <villas/nodes/comedi.hpp>
#include <villas/utils.hpp>
2020-07-04 22:03:56 +02:00
#include <villas/exceptions.hpp>
2018-03-28 19:07:00 +02:00
2020-07-04 22:03:56 +02:00
using namespace villas;
2021-05-10 00:12:30 +02:00
using namespace villas::node;
2019-06-04 16:55:38 +02:00
using namespace villas::utils;
/* Utility functions to dump a comedi_cmd graciously taken from comedilib demo */
static char* comedi_cmd_trigger_src(unsigned int src, char *buf);
2021-02-16 14:15:14 +01:00
static void comedi_dump_cmd(Logger logger, comedi_cmd *cmd);
2021-02-16 14:15:14 +01:00
static int comedi_parse_direction(struct comedi *c, struct comedi_direction *d, json_t *json)
2018-03-29 15:40:00 +02:00
{
int ret;
json_t *json_chans;
json_error_t err;
/* Default values */
2018-03-29 15:40:00 +02:00
d->subdevice = -1;
d->buffer_size = 16;
2018-03-29 15:40:00 +02:00
2021-02-16 14:15:14 +01:00
ret = json_unpack_ex(json, &err, 0, "{ s?: i, s?: i, s: o, s: i }",
2018-03-29 15:40:00 +02:00
"subdevice", &d->subdevice,
"bufsize", &d->buffer_size,
"signals", &json_chans,
"rate", &d->sample_rate_hz
2018-03-29 15:40:00 +02:00
);
if (ret)
2021-02-16 14:15:14 +01:00
throw ConfigError(json, err, "node-config-node-comedi");
2018-03-29 15:40:00 +02:00
if (!json_is_array(json_chans))
return -1;
/* Convert kilobytes to bytes */
d->buffer_size = d->buffer_size << 10;
2018-03-29 15:40:00 +02:00
size_t i;
json_t *json_chan;
2019-01-14 19:07:57 +01:00
d->chanlist_len = json_array_size(json_chans);
2021-02-16 14:15:14 +01:00
if (d->chanlist_len == 0)
throw ConfigError(json_chans, "node-config-node-channels", "No channels configured");
d->chanlist = new unsigned int[d->chanlist_len];
if (!d->chanlist)
throw MemoryAllocationError();
d->chanspecs = new comedi_chanspec[d->chanlist_len];
if (!d->chanspecs)
throw MemoryAllocationError();
2018-03-29 15:40:00 +02:00
json_array_foreach(json_chans, i, json_chan) {
int num, range, aref;
ret = json_unpack_ex(json_chan, &err, 0, "{ s: i, s: i, s: i }",
2021-02-16 14:15:14 +01:00
"channel", &num,
"range", &range,
"aref", &aref
);
if (ret)
2021-02-16 14:15:14 +01:00
throw ConfigError(json_chan, err, "node-config-node-comedi");
2018-03-29 15:40:00 +02:00
if (aref < AREF_GROUND || aref > AREF_OTHER)
2021-02-16 14:15:14 +01:00
throw ConfigError(json_chan, "node-config-node-comedi-aref", "Invalid value for analog reference: aref={}", aref);
2018-03-29 15:40:00 +02:00
d->chanlist[i] = CR_PACK(num, range, aref);
2018-03-29 15:40:00 +02:00
}
return 0;
}
2020-08-25 21:00:52 +02:00
static int comedi_start_common(struct vnode *n)
{
struct comedi *c = (struct comedi *) n->_vd;
struct comedi_direction* directions[2] = { &c->in, &c->out };
comedi_set_global_oor_behavior(COMEDI_OOR_NAN);
for (int dirIdx = 0; dirIdx < 2; dirIdx++) {
struct comedi_direction* d = directions[dirIdx];
int ret;
if (!d->present)
continue;
/* Sanity-check channel config and populate chanspec for later */
2019-04-22 23:43:46 +02:00
for (unsigned i = 0; i < d->chanlist_len; i++) {
const unsigned int channel = CR_CHAN(d->chanlist[i]);
const int range = CR_RANGE(d->chanlist[i]);
ret = comedi_get_n_ranges(c->dev, d->subdevice, channel);
if (ret < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to get ranges for channel {} on subdevice {}", channel, d->subdevice);
if (range >= ret)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Invalid range for channel {} on subdevice {}: range={}", channel, d->subdevice, range);
ret = comedi_get_maxdata(c->dev, d->subdevice, channel);
if (ret <= 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to get max. data value for channel {} on subdevice {}", channel, d->subdevice);
d->chanspecs[i].maxdata = ret;
d->chanspecs[i].range = comedi_get_range(c->dev, d->subdevice,
channel, range);
2021-02-16 14:15:14 +01:00
n->logger->info("{} channel: {} aref={} range={} maxdata={}",
(d == &c->in ? "Input" : "Output"), channel,
CR_AREF(d->chanlist[i]), range, d->chanspecs[i].maxdata);
}
const int flags = comedi_get_subdevice_flags(c->dev, d->subdevice);
d->sample_size = (flags & SDF_LSAMPL) ? sizeof(lsampl_t) : sizeof(sampl_t);
/* Set buffer size */
comedi_set_buffer_size(c->dev, d->subdevice, d->buffer_size);
comedi_set_max_buffer_size(c->dev, d->subdevice, d->buffer_size);
ret = comedi_get_buffer_size(c->dev, d->subdevice);
if (ret != d->buffer_size)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to set buffer size for subdevice {}", d->subdevice);
2021-02-16 14:15:14 +01:00
n->logger->info("Set buffer size for subdevice {} to {} bytes", d->subdevice, d->buffer_size);
ret = comedi_lock(c->dev, d->subdevice);
if (ret)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to lock subdevice {}", d->subdevice);
}
return 0;
}
2020-08-25 21:00:52 +02:00
static int comedi_start_in(struct vnode *n)
2018-03-29 15:40:00 +02:00
{
int ret;
struct comedi *c = (struct comedi *) n->_vd;
struct comedi_direction *d = &c->in;
/* Try to find first analog input subdevice if not specified in config */
if (d->subdevice < 0) {
d->subdevice = comedi_find_subdevice_by_type(c->dev, COMEDI_SUBD_AI, 0);
2018-03-29 15:40:00 +02:00
if (d->subdevice < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Cannot find analog input device for node '{}'");
}
else {
/* Check if subdevice is usable */
ret = comedi_get_subdevice_type(c->dev, d->subdevice);
if (ret != COMEDI_SUBD_AI)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Input subdevice is not an analog input");
2018-03-29 15:40:00 +02:00
}
ret = comedi_get_subdevice_flags(c->dev, d->subdevice);
if (ret < 0 || !(ret & SDF_CMD_READ))
2021-02-16 14:15:14 +01:00
throw RuntimeError("Input subdevice does not support 'read' commands");
2018-03-29 15:40:00 +02:00
comedi_set_read_subdevice(c->dev, d->subdevice);
ret = comedi_get_read_subdevice(c->dev);
if (ret < 0 || ret != d->subdevice)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to change 'read' subdevice from {} to {}", ret, d->subdevice);
2018-03-29 15:40:00 +02:00
comedi_cmd cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.subdev = d->subdevice;
/* Make card send interrupts after every sample, not only when fifo is half
* full (TODO: evaluate if this makes sense, leave as reminder) */
//cmd.flags = TRIG_WAKE_EOS;
/* Start right now */
cmd.start_src = TRIG_NOW;
/* Trigger scans periodically */
cmd.scan_begin_src = TRIG_TIMER;
cmd.scan_begin_arg = 1e9 / d->sample_rate_hz;
/* Do conversions in serial with 1ns inter-conversion delay */
cmd.convert_src = TRIG_TIMER;
cmd.convert_arg = 1; /* Inter-conversion delay in nanoseconds */
/* Terminate scan after each channel has been converted */
cmd.scan_end_src = TRIG_COUNT;
2019-01-14 19:07:57 +01:00
cmd.scan_end_arg = d->chanlist_len;
/* Contionous sampling */
cmd.stop_src = TRIG_NONE;
cmd.chanlist = d->chanlist;
2019-01-14 19:07:57 +01:00
cmd.chanlist_len = d->chanlist_len;
/* First run might change command, second should return successfully */
ret = comedi_command_test(c->dev, &cmd);
ret = comedi_command_test(c->dev, &cmd);
if (ret < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Invalid command for input subdevice");
2021-02-16 14:15:14 +01:00
n->logger->info("Input command:");
comedi_dump_cmd(n->logger, &cmd);
ret = comedi_command(c->dev, &cmd);
if (ret < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to issue command to input subdevice");
d->started = time_now();
d->counter = 0;
d->running = true;
#if COMEDI_USE_READ
/* Be prepared to consume one entire buffer */
c->buf = new char[c->in.buffer_size];
c->bufptr = c->buf;
if (!c->buf)
throw MemoryAllocationError();
2021-02-16 14:15:14 +01:00
n->logger->info("Compiled for kernel read() interface");
#else
2021-02-16 14:15:14 +01:00
n->logger->info("Compiled for kernel mmap() interface");
2018-03-29 15:40:00 +02:00
#endif
return 0;
}
2020-08-25 21:00:52 +02:00
static int comedi_start_out(struct vnode *n)
2018-03-29 15:40:00 +02:00
{
int ret;
struct comedi *c = (struct comedi *) n->_vd;
struct comedi_direction *d = &c->out;
/* Try to find first analog output subdevice if not specified in config */
if (d->subdevice < 0) {
d->subdevice = comedi_find_subdevice_by_type(c->dev, COMEDI_SUBD_AO, 0);
if (d->subdevice < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Cannot find analog output device");
}
else {
ret = comedi_get_subdevice_type(c->dev, d->subdevice);
if (ret != COMEDI_SUBD_AO)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Output subdevice is not an analog output");
}
2018-03-29 15:40:00 +02:00
ret = comedi_get_subdevice_flags(c->dev, d->subdevice);
if (ret < 0 || !(ret & SDF_CMD_WRITE))
2021-02-16 14:15:14 +01:00
throw RuntimeError("Output subdevice does not support 'write' commands");
2018-03-29 15:40:00 +02:00
comedi_set_write_subdevice(c->dev, d->subdevice);
ret = comedi_get_write_subdevice(c->dev);
if (ret < 0 || ret != d->subdevice)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to change 'write' subdevice from {} to {}", ret, d->subdevice);
comedi_cmd cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.subdev = d->subdevice;
cmd.flags = CMDF_WRITE;
/* Wait for internal trigger, we will have to fill the buffer first */
cmd.start_src = TRIG_INT;
cmd.start_arg = 0;
cmd.scan_begin_src = TRIG_TIMER;
cmd.scan_begin_arg = 1e9 / d->sample_rate_hz;
cmd.convert_src = TRIG_NOW;
cmd.convert_arg = 0;
cmd.scan_end_src = TRIG_COUNT;
2019-01-14 19:07:57 +01:00
cmd.scan_end_arg = d->chanlist_len;
/* Continous sampling */
cmd.stop_src = TRIG_NONE;
cmd.stop_arg = 0;
cmd.chanlist = d->chanlist;
2019-01-14 19:07:57 +01:00
cmd.chanlist_len = d->chanlist_len;
/* First run might change command, second should return successfully */
ret = comedi_command_test(c->dev, &cmd);
if (ret < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Invalid command for input subdevice");
ret = comedi_command_test(c->dev, &cmd);
if (ret < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Invalid command for input subdevice");
2021-02-16 14:15:14 +01:00
n->logger->info("Output command:");
comedi_dump_cmd(n->logger, &cmd);
ret = comedi_command(c->dev, &cmd);
if (ret < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to issue command to input subdevice of node '{}'");
/* Output will only start after the internal trigger */
d->running = false;
2018-06-15 19:29:23 +02:00
d->last_debug = time_now();
2021-02-16 14:15:14 +01:00
/* Allocate buffer for one complete VILLAS sample */
/** @todo: maybe increase buffer size according to c->vectorize */
2019-01-14 19:07:57 +01:00
const size_t local_buffer_size = d->sample_size * d->chanlist_len;
d->buffer = new char[local_buffer_size];
d->bufptr = d->buffer;
if (!d->buffer)
throw MemoryAllocationError();
/* Initialize local buffer used for write() syscalls */
2019-04-22 23:43:46 +02:00
for (unsigned channel = 0; channel < d->chanlist_len; channel++) {
const unsigned raw = comedi_from_phys(0.0f, d->chanspecs[channel].range, d->chanspecs[channel].maxdata);
if (d->sample_size == sizeof(sampl_t))
*((sampl_t *)d->bufptr) = raw;
else
*((lsampl_t *)d->bufptr) = raw;
d->bufptr += d->sample_size;
}
/* Preload comedi output buffer */
2019-04-22 23:43:46 +02:00
for (unsigned i = 0; i < d->buffer_size / local_buffer_size; i++) {
size_t written = write(comedi_fileno(c->dev), d->buffer, local_buffer_size);
if (written != local_buffer_size) {
2021-02-16 14:15:14 +01:00
throw RuntimeError("Cannot preload Comedi buffer");
}
}
2018-03-29 15:40:00 +02:00
2019-01-14 19:07:57 +01:00
const size_t villas_samples_in_kernel_buf = d->buffer_size / (d->sample_size * d->chanlist_len);
2021-02-16 14:15:14 +01:00
const double latencyMs = (double) villas_samples_in_kernel_buf / d->sample_rate_hz * 1e3;
n->logger->info("Added latency due to buffering: {:4.1f} ms", latencyMs);
2018-06-15 19:29:23 +02:00
2018-03-29 15:40:00 +02:00
return 0;
}
2020-08-25 21:00:52 +02:00
static int comedi_stop_in(struct vnode *n)
2018-03-29 15:40:00 +02:00
{
int ret;
struct comedi *c = (struct comedi *) n->_vd;
struct comedi_direction *d = &c->in;
comedi_cancel(c->dev, d->subdevice);
ret = comedi_unlock(c->dev, d->subdevice);
2018-03-29 15:40:00 +02:00
if (ret)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to lock subdevice {}", d->subdevice);
2018-03-29 15:40:00 +02:00
return 0;
}
2020-08-25 21:00:52 +02:00
static int comedi_stop_out(struct vnode *n)
2018-03-29 15:40:00 +02:00
{
int ret;
struct comedi *c = (struct comedi *) n->_vd;
struct comedi_direction *d = &c->out;
comedi_cancel(c->dev, d->subdevice);
ret = comedi_unlock(c->dev, d->subdevice);
2018-03-29 15:40:00 +02:00
if (ret)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to lock subdevice {}", d->subdevice);
2018-03-29 15:40:00 +02:00
return 0;
}
2021-02-16 14:15:14 +01:00
int comedi_parse(struct vnode *n, json_t *json)
2018-03-28 19:07:00 +02:00
{
int ret;
struct comedi *c = (struct comedi *) n->_vd;
const char *device;
2019-04-22 23:45:38 +02:00
json_t *json_in = nullptr;
json_t *json_out = nullptr;
json_error_t err;
2021-02-16 14:15:14 +01:00
ret = json_unpack_ex(json, &err, 0, "{ s: s, s?: o, s?: o }",
"device", &device,
"in", &json_in,
"out", &json_out);
2018-03-28 19:07:00 +02:00
if (ret)
2021-02-16 14:15:14 +01:00
throw ConfigError(json, err, "node-config-node-comedi");
2018-03-28 19:07:00 +02:00
2019-04-22 23:45:38 +02:00
c->in.present = json_in != nullptr;
c->in.enabled = false;
c->in.running = false;
2019-04-22 23:45:38 +02:00
c->out.present = json_out != nullptr;
c->out.enabled = false;
c->out.running = false;
if (c->in.present) {
ret = comedi_parse_direction(c, &c->in, json_in);
2018-03-29 15:40:00 +02:00
if (ret)
return ret;
2018-03-29 15:40:00 +02:00
}
if (c->out.present) {
ret = comedi_parse_direction(c, &c->out, json_out);
2018-03-29 15:40:00 +02:00
if (ret)
return ret;
2018-03-29 15:40:00 +02:00
}
c->device = strdup(device);
2018-03-28 19:07:00 +02:00
return 0;
}
2020-08-25 21:00:52 +02:00
char * comedi_print(struct vnode *n)
2018-03-28 19:07:00 +02:00
{
struct comedi *c = (struct comedi *) n->_vd;
2019-04-22 23:45:38 +02:00
char *buf = nullptr;
2018-03-28 19:07:00 +02:00
const char *board = comedi_get_board_name(c->dev);
const char *driver = comedi_get_driver_name(c->dev);
2018-03-29 15:40:00 +02:00
strcatf(&buf, "board=%s, driver=%s, device=%s", board, driver, c->device);
2018-03-28 19:07:00 +02:00
return buf;
}
2020-08-25 21:00:52 +02:00
int comedi_start(struct vnode *n)
2018-03-28 19:07:00 +02:00
{
struct comedi *c = (struct comedi *) n->_vd;
c->dev = comedi_open(c->device);
2021-02-16 14:15:14 +01:00
if (!c->dev)
throw RuntimeError("Failed to open device: {}", comedi_strerror(comedi_errno()));
/* Enable non-blocking syscalls */
/** @todo: verify if this works with both input and output, so comment out */
//if (fcntl(comedi_fileno(c->dev), F_SETFL, O_NONBLOCK))
2021-02-16 14:15:14 +01:00
// throw RuntimeError("Failed to set non-blocking flag in Comedi FD");
comedi_start_common(n);
if (c->in.present) {
int ret = comedi_start_in(n);
if (ret)
return ret;
c->in.enabled = true;
}
if (c->out.present) {
int ret = comedi_start_out(n);
if (ret)
return ret;
c->out.enabled = true;
}
#if !COMEDI_USE_READ
2021-02-16 14:15:14 +01:00
n->logger->info("Mapping Comedi buffer of {} bytes", c->in.buffer_size);
2019-04-22 23:45:38 +02:00
c->map = mmap(nullptr, c->in.buffer_size, PROT_READ, MAP_SHARED, comedi_fileno(c->dev), 0);
if (c->map == MAP_FAILED)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to map Comedi buffer");
c->front = 0;
c->back = 0;
c->bufpos = 0;
#endif
2018-03-29 15:40:00 +02:00
2018-03-28 19:07:00 +02:00
return 0;
}
2020-08-25 21:00:52 +02:00
int comedi_stop(struct vnode *n)
2018-03-28 19:07:00 +02:00
{
int ret;
struct comedi *c = (struct comedi *) n->_vd;
if (c->in.enabled)
comedi_stop_in(n);
if (c->out.enabled)
comedi_stop_out(n);
2018-03-29 15:40:00 +02:00
ret = comedi_close(c->dev);
if (ret)
return ret;
2018-03-28 19:07:00 +02:00
return 0;
}
#if COMEDI_USE_READ
2021-05-10 00:12:30 +02:00
int comedi_read(struct vnode *n, struct sample * const smps[], unsigned cnt)
2018-03-28 19:07:00 +02:00
{
int ret;
struct comedi *c = (struct comedi *) n->_vd;
struct comedi_direction *d = &c->in;
2019-01-14 19:07:57 +01:00
const size_t villas_sample_size = d->chanlist_len * d->sample_size;
ret = comedi_get_buffer_contents(c->dev, d->subdevice);
if (ret < 0) {
if (comedi_errno() == EBUF_OVR)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Comedi buffer overflow");
else
2021-02-16 14:15:14 +01:00
throw RuntimeError("Comedi error: {}", comedi_strerror(comedi_errno()));
}
fd_set rdset;
FD_ZERO(&rdset);
FD_SET(comedi_fileno(c->dev), &rdset);
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 5000;
2019-04-22 23:45:38 +02:00
ret = select(comedi_fileno(c->dev) + 1, &rdset, nullptr, nullptr, &timeout);
if (ret < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed select()");
else if (ret == 0) /* hit timeout */
return 0;
else if (FD_ISSET(comedi_fileno(c->dev), &rdset)) { /* comedi file descriptor became ready */
const size_t buffer_bytes_free = d->buffer_size - (c->bufptr - c->buf);
const size_t bytes_requested = cnt * villas_sample_size;
ret = read(comedi_fileno(c->dev), c->bufptr, MIN(bytes_requested, buffer_bytes_free));
if (ret < 0) {
if (errno == EAGAIN)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed read()");
else
return 0;
}
else if (ret == 0) {
2021-02-16 14:15:14 +01:00
n->logger->warn("Timeout in select(), no samples available");
return 0;
}
else {
/* Sample handling here */
const size_t bytes_available = ret;
const size_t raw_samples_available = bytes_available / d->sample_size;
2019-01-14 19:07:57 +01:00
const size_t villas_samples_available = raw_samples_available / d->chanlist_len;
2021-02-16 14:15:14 +01:00
n->logger->info("There are {} bytes available ({} requested) => {} VILLAS samples",
bytes_available, bytes_requested, villas_samples_available);
if (cnt > villas_samples_available)
cnt = villas_samples_available;
for (size_t i = 0; i < cnt; i++) {
d->counter++;
smps[i]->signals = &n->in.signals;
2019-06-23 16:13:23 +02:00
smps[i]->flags = (int) SampleFlags::HAS_TS_ORIGIN | (int) SampleFlags::HAS_DATA | (int) SampleFlags::HAS_SEQUENCE;
2019-01-14 19:07:57 +01:00
smps[i]->sequence = d->counter / d->chanlist_len;
struct timespec offset = time_from_double(d->counter * 1.0 / d->sample_rate_hz);
smps[i]->ts.origin = time_add(&d->started, &offset);
2019-01-14 19:07:57 +01:00
smps[i]->length = d->chanlist_len;
2021-02-16 14:15:14 +01:00
if (smps[i]->capacity < d->chanlist_len)
throw RuntimeError("Sample has insufficient capacity: {} < {}", smps[i]->capacity, d->chanlist_len);
2019-04-22 23:43:46 +02:00
for (unsigned si = 0; si < d->chanlist_len; si++) {
unsigned int raw;
if (d->sample_size == sizeof(sampl_t))
raw = *((sampl_t *)(c->bufptr));
else
raw = *((lsampl_t *)(c->bufptr));
c->bufptr += d->sample_size;
smps[i]->data[si].f = comedi_to_phys(raw, d->chanspecs[si].range, d->chanspecs[si].maxdata);
if (std::isnan(smps[i]->data[si].f))
2021-02-16 14:15:14 +01:00
n->logger->warn("Input: channel {} clipped", CR_CHAN(d->chanlist[si]));
}
}
const size_t bytes_consumed = cnt * villas_sample_size;
const size_t bytes_left = bytes_available - bytes_consumed;
if (bytes_left > 0) {
/* Move leftover bytes to the beginning of buffer */
/** @todo: optimize? */
memmove(c->buf, c->bufptr, bytes_left);
}
2021-02-16 14:15:14 +01:00
n->logger->info("Consumed {} bytes", bytes_consumed);
/* Start at the beginning again */
c->bufptr = c->buf;
return cnt;
}
}
2021-02-16 14:15:14 +01:00
else
/* unknown file descriptor became ready */
2021-02-16 14:15:14 +01:00
n->logger->warn("Unknown file descriptor ready");
2018-03-28 19:07:00 +02:00
return -1;
}
#else
2021-05-10 00:12:30 +02:00
int comedi_read(struct vnode *n, struct sample * const smps[], unsigned cnt)
{
int ret;
struct comedi *c = (struct comedi *) n->_vd;
struct comedi_direction *d = &c->in;
2019-01-14 19:07:57 +01:00
const size_t villas_sample_size = d->chanlist_len * d->sample_size;
comedi_set_read_subdevice(c->dev, d->subdevice);
2021-02-16 14:15:14 +01:00
n->logger->info("Current bufpos={}", c->bufpos);
#if 0
if (c->bufpos > (d->buffer_size - villas_sample_size)) {
ret = comedi_get_buffer_read_offset(c->dev, d->subdevice);
if (ret < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Canot get offset");
c->bufpos = ret;
2021-02-16 14:15:14 +01:00
n->logger->info("Change bufpos={}", c->bufpos);
}
#endif
ret = comedi_get_buffer_contents(c->dev, d->subdevice);
if (ret == 0)
return 0;
else if (ret < 0) {
if (comedi_errno() == EBUF_OVR)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Comedi buffer overflow");
else
2021-02-16 14:15:14 +01:00
throw RuntimeError("Comedi error: {}", comedi_strerror(comedi_errno()));
}
const size_t bytes_available = ret;
const size_t raw_sample_count = bytes_available / d->sample_size;
2019-01-14 19:07:57 +01:00
size_t villas_sample_count = raw_sample_count / d->chanlist_len;
if (villas_sample_count == 0)
return 0;
2021-02-16 14:15:14 +01:00
n->logger->info("There are {} VILLAS samples ({} raw bytes, {} channels)", villas_sample_count, bytes_available, d->chanlist_len);
#if 0
if (villas_sample_count == 1)
2021-02-16 14:15:14 +01:00
n->logger->info("front={} back={} bufpos={}", c->front, c->back, c->bufpos);
if ((c->bufpos + bytes_available) >= d->buffer_size) {
/* Let comedi do the wraparound, only consume until end of buffer */
villas_sample_count = (d->buffer_size - c->bufpos) / villas_sample_size;
2021-02-16 14:15:14 +01:00
n->logger->warn("Reducing consumption from {} to {} bytes", ret, bytes_available);
n->logger->warn("Only consume {} VILLAS samples b/c of buffer wraparound", villas_sample_count);
}
#endif
if (cnt > villas_sample_count)
cnt = villas_sample_count;
#if 0
if (bytes_available != 0 && bytes_available < villas_sample_size) {
2021-02-16 14:15:14 +01:00
n->logger->warn("Cannot consume samples, only {} bytes available, throw away", ret);
ret = comedi_mark_buffer_read(c->dev, d->subdevice, bytes_available);
if (ret != bytes_available)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Cannot throw away {} bytes, returned {}", bytes_available, ret);
return 0;
}
#endif
const size_t samples_total_bytes = cnt * villas_sample_size;
ret = comedi_mark_buffer_read(c->dev, d->subdevice, samples_total_bytes);
if (ret == 0) {
2021-02-16 14:15:14 +01:00
n->logger->warn("Marking read buffer ({} bytes) not working, try again later", samples_total_bytes);
return 0;
}
else if (ret != samples_total_bytes) {
2021-02-16 14:15:14 +01:00
n->logger->warn("Can only mark {} bytes as read, reducing samples", ret);
return 0;
}
else
2021-02-16 14:15:14 +01:00
n->logger->info("Consume {} bytes", ret);
/* Align front to whole samples */
c->front = c->back + samples_total_bytes;
for (size_t i = 0; i < cnt; i++) {
d->counter++;
2019-06-23 16:13:23 +02:00
smps[i]->flags = (int) SampleFlags::HAS_TS_ORIGIN | (int) SampleFlags::HAS_DATA | (int) SampleFlags::HAS_SEQUENCE;
2019-01-14 19:07:57 +01:00
smps[i]->sequence = d->counter / d->chanlist_len;
struct timespec offset = time_from_double(d->counter * 1.0 / d->sample_rate_hz);
smps[i]->ts.origin = time_add(&d->started, &offset);
2019-01-14 19:07:57 +01:00
smps[i]->length = d->chanlist_len;
2019-01-14 19:07:57 +01:00
if (smps[i]->capacity < d->chanlist_len)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Sample has insufficient capacity: {} < {}", smps[i]->capacity, d->chanlist_len);
2019-01-14 19:07:57 +01:00
for (int si = 0; si < d->chanlist_len; si++) {
unsigned int raw;
if (d->sample_size == sizeof(sampl_t))
raw = *((sampl_t *)(c->map + c->bufpos));
else
raw = *((lsampl_t *)(c->map + c->bufpos));
smps[i]->data[si].f = comedi_to_phys(raw, d->chanspecs[si].range, d->chanspecs[si].maxdata);
2021-02-16 14:15:14 +01:00
if (isnan(smps[i]->data[si].f))
throw RuntimeError("Got nan");
// smps[i]->data[si].i = raw;
c->bufpos += d->sample_size;
if (c->bufpos >= d->buffer_size) {
2021-02-16 14:15:14 +01:00
n->logger->warn("Read buffer wraparound");
// c->bufpos = 0;
}
}
}
// const size_t bytes_consumed = c->front - c->back;
2021-02-16 14:15:14 +01:00
// n->logger->info("Advance Comedi buffer by {} bytes", bytes_consumed);
ret = comedi_get_buffer_read_offset(c->dev, d->subdevice);
if (ret < 0) {
if (comedi_errno() != EPIPE)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to get read buffer offset: {}, Comedi error {}", ret, comedi_strerror(comedi_errno()));
else
ret = c->bufpos;
}
2021-02-16 14:15:14 +01:00
n->logger->warn("Change bufpos: {} to {}", c->bufpos, ret);
c->bufpos = ret;
#if 0
ret = comedi_mark_buffer_read(c->dev, d->subdevice, bytes_consumed);
if (ret < 0) //!= bytes_consumed)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to mark buffer position (ret={}) for input stream", ret);
// else if (ret == 0) {
else {
2021-02-16 14:15:14 +01:00
n->logger->info("Consumed {} bytes", bytes_consumed);
n->logger->info("Mark buffer returned {}", ret);
if (ret == 0) {
ret = comedi_mark_buffer_read(c->dev, d->subdevice, bytes_consumed);
2021-02-16 14:15:14 +01:00
n->logger->info("Trying again, mark buffer returned now {}", ret);
}
if (ret > 0) {
ret = comedi_get_buffer_read_offset(c->dev, d->subdevice);
if (ret < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to get read buffer offset");
2021-02-16 14:15:14 +01:00
n->logger->warn("Change bufpos1: {} to {}", c->bufpos, ret);
c->bufpos = ret;
}
else {
2021-02-16 14:15:14 +01:00
// n->logger->warn("Change bufpos2: {} to {}", c->bufpos, c->);
// c->bufpos += bytes_consumed;
2021-02-16 14:15:14 +01:00
n->logger->warn("Keep bufpos={}", c->bufpos);
}
// c->bufpos = 0;
}
#endif
2021-02-16 14:15:14 +01:00
// n->logger->info("New bufpos: {}", c->bufpos);
c->back = c->front;
return cnt;
}
#endif
2021-05-10 00:12:30 +02:00
int comedi_write(struct vnode *n, struct sample * const smps[], unsigned cnt)
2018-03-28 19:07:00 +02:00
{
int ret;
struct comedi *c = (struct comedi *) n->_vd;
struct comedi_direction *d = &c->out;
2018-03-28 19:07:00 +02:00
if (!d->enabled) {
2021-02-16 14:15:14 +01:00
n->logger->warn("Attempting to write, but output is not enabled");
return 0;
}
if (!d->running) {
/* Output was not yet running, so start now */
ret = comedi_internal_trigger(c->dev, d->subdevice, 0);
if (ret < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Failed to trigger-start output");
d->started = time_now();
d->counter = 0;
d->running = true;
2021-02-16 14:15:14 +01:00
n->logger->info("Starting output");
}
const size_t buffer_capacity_raw = d->buffer_size / d->sample_size;
2019-01-14 19:07:57 +01:00
const size_t buffer_capacity_villas = buffer_capacity_raw / d->chanlist_len;
const size_t villas_sample_size = d->sample_size * d->chanlist_len;
ret = comedi_get_buffer_contents(c->dev, d->subdevice);
if (ret < 0) {
if (comedi_errno() == EBUF_OVR)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Comedi buffer overflow");
else
2021-02-16 14:15:14 +01:00
throw RuntimeError("Comedi error: {}", comedi_strerror(comedi_errno()));
}
const size_t bytes_in_buffer = ret;
const size_t raw_samples_in_buffer = bytes_in_buffer / d->sample_size;
2019-01-14 19:07:57 +01:00
const size_t villas_samples_in_buffer = raw_samples_in_buffer / d->chanlist_len;
if (villas_samples_in_buffer == buffer_capacity_villas) {
2021-02-16 14:15:14 +01:00
n->logger->warn("Comedi buffer is full");
return 0;
}
else {
2018-06-15 19:29:23 +02:00
struct timespec now = time_now();
if (time_delta(&d->last_debug, &now) >= 1) {
2021-02-16 14:15:14 +01:00
n->logger->debug("Comedi write buffer: {} VILLAS samples ({}% of buffer)",
villas_samples_in_buffer,
(100.0f * villas_samples_in_buffer / buffer_capacity_villas));
2018-06-15 19:29:23 +02:00
d->last_debug = time_now();
}
}
size_t villas_samples_written = 0;
while (villas_samples_written < cnt) {
2021-05-10 00:12:30 +02:00
const struct sample *sample = smps[villas_samples_written];
2019-01-14 19:07:57 +01:00
if (sample->length != d->chanlist_len)
2021-02-16 14:15:14 +01:00
throw RuntimeError("Value count in sample ({}) != configured output channels ({})", sample->length, d->chanlist_len);
d->bufptr = d->buffer;
/* Move samples from villas into local buffer for comedi */
2019-04-22 23:43:46 +02:00
for (unsigned si = 0; si < sample->length; si++) {
unsigned raw_value = 0;
switch (sample_format(sample, si)) {
2019-06-23 16:13:23 +02:00
case SignalType::FLOAT:
raw_value = comedi_from_phys(sample->data[si].f, d->chanspecs[si].range, d->chanspecs[si].maxdata);
break;
2019-06-23 16:13:23 +02:00
case SignalType::INTEGER:
/* Treat sample as already raw DAC value */
raw_value = sample->data[si].i;
break;
2019-06-23 16:13:23 +02:00
case SignalType::BOOLEAN:
raw_value = comedi_from_phys(sample->data[si].b ? 1 : 0, d->chanspecs[si].range, d->chanspecs[si].maxdata);
break;
2019-06-23 16:13:23 +02:00
case SignalType::COMPLEX:
/* We only output the real part */
raw_value = comedi_from_phys(std::real(sample->data[si].z), d->chanspecs[si].range, d->chanspecs[si].maxdata);
break;
2019-06-23 16:13:23 +02:00
case SignalType::INVALID:
raw_value = 0;
break;
}
if (d->sample_size == sizeof(sampl_t))
*((sampl_t *)d->bufptr) = raw_value;
else
*((lsampl_t *)d->bufptr) = raw_value;
d->bufptr += d->sample_size;
}
/* Try to write one complete villas sample to comedi */
2019-04-22 23:43:46 +02:00
size_t written = write(comedi_fileno(c->dev), d->buffer, villas_sample_size);
if (written < 0)
2021-02-16 14:15:14 +01:00
throw RuntimeError("write() failed");
2019-04-22 23:43:46 +02:00
else if (written == 0)
break; /* Comedi doesn't accept any more samples at the moment */
2019-04-22 23:43:46 +02:00
else if (written == villas_sample_size)
villas_samples_written++;
else
2021-02-16 14:15:14 +01:00
throw RuntimeError("Only partial sample written ({} bytes), oops", written);
}
2021-02-16 14:15:14 +01:00
if (villas_samples_written == 0)
n->logger->warn("Nothing done");
d->counter += villas_samples_written;
return villas_samples_written;
}
char* comedi_cmd_trigger_src(unsigned int src, char *buf)
{
buf[0] = 0;
if (src & TRIG_NONE) strcat(buf, "none|");
if (src & TRIG_NOW) strcat(buf, "now|");
if (src & TRIG_FOLLOW) strcat(buf, "follow|");
if (src & TRIG_TIME) strcat(buf, "time|");
if (src & TRIG_TIMER) strcat(buf, "timer|");
if (src & TRIG_COUNT) strcat(buf, "count|");
if (src & TRIG_EXT) strcat(buf, "ext|");
if (src & TRIG_INT) strcat(buf, "int|");
if (strlen(buf) == 0)
sprintf(buf, "unknown(0x%08x)", src);
else
buf[strlen(buf) - 1] = 0;
return buf;
2018-03-28 19:07:00 +02:00
}
2021-02-16 14:15:14 +01:00
void comedi_dump_cmd(Logger logger, comedi_cmd *cmd)
{
char buf[256];
char* src;
2021-02-16 14:15:14 +01:00
logger->debug("subdevice: {}", cmd->subdev);
src = comedi_cmd_trigger_src(cmd->start_src, buf);
2021-02-16 14:15:14 +01:00
logger->debug("start: {:-8s} {}", src, cmd->start_arg);
src = comedi_cmd_trigger_src(cmd->scan_begin_src, buf);
2021-02-16 14:15:14 +01:00
logger->debug("scan_begin: {:-8s} {}", src, cmd->scan_begin_arg);
src = comedi_cmd_trigger_src(cmd->convert_src, buf);
2021-02-16 14:15:14 +01:00
logger->debug("convert: {:-8s} {}", src, cmd->convert_arg);
src = comedi_cmd_trigger_src(cmd->scan_end_src, buf);
2021-02-16 14:15:14 +01:00
logger->debug("scan_end: {:-8s} {}", src, cmd->scan_end_arg);
src = comedi_cmd_trigger_src(cmd->stop_src,buf);
2021-02-16 14:15:14 +01:00
logger->debug("stop: {:-8s} {}", src, cmd->stop_arg);
}
2020-08-25 21:00:52 +02:00
int comedi_poll_fds(struct vnode *n, int fds[])
2018-03-28 19:07:00 +02:00
{
2018-03-29 15:40:00 +02:00
struct comedi *c = (struct comedi *) n->_vd;
fds[0] = comedi_fileno(c->dev);
return 0;
2018-03-28 19:07:00 +02:00
}
2021-06-21 16:11:42 -04:00
static struct vnode_type p;
__attribute__((constructor(110)))
static void register_plugin() {
2021-06-21 16:11:42 -04:00
p.name = "comedi";
p.description = "Comedi-compatible DAQ/ADC cards";
p.vectorize = 0;
p.size = sizeof(struct comedi);
p.parse = comedi_parse;
p.print = comedi_print;
p.start = comedi_start;
p.stop = comedi_stop;
p.read = comedi_read;
p.write = comedi_write;
p.poll_fds = comedi_poll_fds;
if (!node_types)
node_types = new NodeTypeList();
node_types->push_back(&p);
}