tidbits: tasty chunks of EVL application code

Signed-off-by: Philippe Gerum <rpm@xenomai.org>
This commit is contained in:
Philippe Gerum 2020-04-08 18:47:02 +02:00
parent 7cfd860d1e
commit a281771a43
4 changed files with 231 additions and 1 deletions

View File

@ -2,7 +2,7 @@
include config.mk
TARGETS := include lib benchmarks utils eshi tests
TARGETS := include lib benchmarks utils eshi tests tidbits
$(MAIN_GOALS): output-Makefile
@for target in $(TARGETS); do \

View File

@ -28,6 +28,7 @@ includedir ?= include
bindir ?= bin
testdir ?= tests
libexecdir ?= libexec
tbitsdir ?= tidbits
export ARCH CROSS_COMPILE CC CXX LD AR UAPI CFLAGS LDFLAGS DESTDIR

33
tidbits/Makefile Normal file
View File

@ -0,0 +1,33 @@
# SPDX-License-Identifier: MIT
include ../config.mk
include ../libversion.mk
SRCFILES := $(wildcard *.c)
TARGETS = $(SRCFILES:%.c=$(O_DIR)/%)
DEPFILES = $(SRCFILES:%.c=$(O_DIR)/%.d)
TIDBITS = oob-spi
CMD_CPPFLAGS := $(BASE_CPPFLAGS) -I. -I../include -I$(O_DIR)/../include
CMD_CFLAGS := $(CMD_CPPFLAGS) $(BASE_CFLAGS)
override CFLAGS := $(CMD_CFLAGS) $(CFLAGS)
CMD_LDFLAGS := $(O_DIR)/../lib/libevl.so.$(EVL_IVERSION) -lpthread -lrt
override LDFLAGS := $(CMD_LDFLAGS) $(LDFLAGS)
$(TARGETS):
all: output-Makefile $(DEPFILES) $(TARGETS)
install: all
$(call inst-cmd,tidbits, \
for bin in $(TIDBITS); do \
$(INSTALL) -D $(O_DIR)/$$bin -t $(DESTDIR)/$(tbitsdir); done)
clean clobber mrproper: output-Makefile
$(Q)$(RM) -f $(TARGETS) $(DEPFILES)
$(O_DIR)/%: %.c $(O_DIR)/%.d
$(call ccld-cmd,$@,$(Q)$(CC) -o $(@) $< $(CFLAGS) $(LDFLAGS))
-include $(DEPFILES)

196
tidbits/oob-spi.c Normal file
View File

@ -0,0 +1,196 @@
/*
* SPDX-License-Identifier: MIT
*
* This tidbit demonstrates out-of-band SPI transfers controlled from
* user-space, using the extension the EVL core adds to the common
* SPIDEV interface. Typical use case: closed-loop control systems
* having stringent requirements, i.e. high frequency and/or low
* jitter. The magic starts with the ioctl(SPI_IOC_ENABLE_OOB_MODE)
* request, check out the code.
*
* Using a Raspberry PI2/3/4, you can easily demo this code with a
* simple loopback test, by shorting PIN #19 (SPI_MOSI) and #21
* (SPI_MISO) on the GPIO 40 pin header. You will need
* CONFIG_DMA_BCM2835_OOB, CONFIG_SPI_BCM2835_OOB and
* CONFIG_SPI_SPIDEV_OOB to be turned on in your PI kernel
* configuration.
*
* Usage:
* ~# oob-spi [/dev/spidevX.Y]
*
* What's so interesting with this test? Check the delay reported for
* the transfer, with and without load. Typically, you could run the
* following stress load in the background:
*
* ~# dd if=/dev/zero of=/dev/null bs=128M &
* ~# while :; do hackbench; done&
*/
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <time.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <stdio.h>
#include <pthread.h>
#include <sched.h>
#include <evl/thread.h>
#include <evl/clock.h>
#include <evl/proxy.h>
#include <linux/spi/spidev.h>
#include <uapi/evl/devices/spidev.h>
static const char *device = "/dev/spidev0.0";
static uint32_t mode = SPI_MODE_0;
static uint8_t bits = 8;
static uint32_t speed = 2500000;
static int len = 140;
static void timespec_sub(struct timespec *__restrict r,
const struct timespec *__restrict t1,
const struct timespec *__restrict t2)
{
r->tv_sec = t1->tv_sec - t2->tv_sec;
r->tv_nsec = t1->tv_nsec - t2->tv_nsec;
if (r->tv_nsec < 0) {
r->tv_sec--;
r->tv_nsec += 1000000000;
}
}
int main(int argc, char *argv[])
{
struct spi_ioc_oob_setup oob_setup;
struct timespec begin, end, delta;
struct sched_param param;
int tfd, devfd, ret, n;
char *tx, *rx;
void *iobuf;
if (argc > 1)
device = argv[1];
/*
* This is usual SPI configuration stuff using the spidev
* interface.
*/
devfd = open(device, O_RDWR);
if (devfd < 0)
error(1, errno, "can't open device %s", device);
ret = ioctl(devfd, SPI_IOC_WR_MODE32, &mode);
if (ret)
error(1, errno, "ioctl(SPI_IOC_WR_MODE32)");
ret = ioctl(devfd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret)
error(1, errno, "ioctl(SPI_IOC_WR_BITS_PER_WORD)");
ret = ioctl(devfd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret)
error(1, errno, "ioctl(SPI_IOC_WR_MAX_SPEED_HZ)");
/*
* This part switches the device to out-of-band operation
* mode. In this case, I/O is performed via the DMA engine
* exclusively, directly from/to buffers (m)mapped into the
* address space of the client.
*/
oob_setup.frame_len = len;
oob_setup.speed_hz = speed;
oob_setup.bits_per_word = bits;
ret = ioctl(devfd, SPI_IOC_ENABLE_OOB_MODE, &oob_setup);
if (ret)
error(1, errno, "ioctl(SPI_IOC_ENABLE_OOB_MODE)");
printf("mapping %d bytes, tx@%d, rx@%d, frame_len=%d\n",
oob_setup.iobuf_len, oob_setup.tx_offset, oob_setup.rx_offset, len);
/*
* We may map the I/O area now, it is composed of two adjacent
* buffers of @len bytes (plus alignment). CUATION: the
* mapping is always on coherent DMA memory (i.e. non-cached).
*/
iobuf = mmap(NULL, oob_setup.iobuf_len, PROT_READ|PROT_WRITE,
MAP_SHARED, devfd, 0);
if (iobuf == MAP_FAILED)
error(1, errno, "mmap()");
/*
* Trick: we want evl_attach_self() to inherit the SCHED_FIFO
* policy and priority for scheduling by the EVL core. We
* could have used evl_set_schedattr() explicitly once
* attached instead.
*/
param.sched_priority = 1;
ret = pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
if (ret)
error(1, ret, "pthread_setschedparam()");
/*
* The core told us where to read and write data on return to
* ioctl(SPI_IOC_ENABLE_OOB_MODE), which is at some offset
* from the I/O buffer we received from mmap().
*/
tx = iobuf + oob_setup.tx_offset;
memset(tx, 0x42, len);
rx = iobuf + oob_setup.rx_offset;
memset(rx, 0, len);
/* Let's attach to the EVL core. */
tfd = evl_attach_self("oob-spi:%d", getpid());
if (tfd < 0)
error(1, -tfd, "cannot attach to the EVL core");
/*
* The actual I/O request: sending from @tx, receiving to
* @rx. Do some trivial test, running a single
* transaction. You may want to try making this a loop.
*/
evl_read_clock(EVL_CLOCK_MONOTONIC, &begin);
ret = oob_ioctl(devfd, SPI_IOC_RUN_OOB_XFER);
if (ret)
error(1, errno, "oob_ioctl(SPI_IOC_RUN_OOB_XFER)");
evl_read_clock(EVL_CLOCK_MONOTONIC, &end);
/*
* Do some visual control of the input buffer we received, it
* should be filled with value 0x42. We could have used
* printf(3) in the dump loop below, but were you to include
* this in an out-of-band loop for obtaining latency figures,
* then you would want evl_printf() to handle the printouts,
* so that no delay is incurred.
*/
timespec_sub(&delta, &end, &begin);
evl_printf("transfer done in %ld s, %ld us:",
delta.tv_sec, delta.tv_nsec / 1000);
/* Dump the contents of the input buffer. */
for (n = 0; n < len; n++) {
if (!(n % 16))
evl_printf("\n");
evl_printf("%.2x ", rx[n]);
}
evl_printf("\n");
/* All done, wrap it up. */
munmap(iobuf, oob_setup.iobuf_len);
ret = ioctl(devfd, SPI_IOC_DISABLE_OOB_MODE);
if (ret)
error(1, errno, "ioctl(SPI_IOC_DISABLE_OOB_MODE)");
close(devfd);
return 0;
}