Module: xenomai-3 Branch: master Commit: 789773177b7d124dac8c652f53a973cf71ee2b2a URL: http://git.xenomai.org/?p=xenomai-3.git;a=commit;h=789773177b7d124dac8c652f53a973cf71ee2b2a
Author: Philippe Gerum <r...@xenomai.org> Date: Sat Jun 25 18:55:14 2016 +0200 testsuite/spitest: add SPI test suite --- configure.ac | 1 + testsuite/Makefile.am | 2 + testsuite/gpiotest/gpiotest.c | 31 +-- testsuite/spitest/Makefile.am | 19 ++ testsuite/spitest/spitest.c | 451 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 491 insertions(+), 13 deletions(-) diff --git a/configure.ac b/configure.ac index 1f3c4fe..1308f8b 100644 --- a/configure.ac +++ b/configure.ac @@ -885,6 +885,7 @@ AC_CONFIG_FILES([ \ testsuite/latency/Makefile \ testsuite/switchtest/Makefile \ testsuite/gpiotest/Makefile \ + testsuite/spitest/Makefile \ testsuite/smokey/Makefile \ testsuite/smokey/arith/Makefile \ testsuite/smokey/sched-quota/Makefile \ diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am index c345472..76d108e 100644 --- a/testsuite/Makefile.am +++ b/testsuite/Makefile.am @@ -5,6 +5,7 @@ if XENO_COBALT SUBDIRS += \ clocktest \ gpiotest \ + spitest \ switchtest \ xeno-test endif @@ -14,5 +15,6 @@ DIST_SUBDIRS = \ gpiotest \ latency \ smokey \ + spitest \ switchtest \ xeno-test diff --git a/testsuite/gpiotest/gpiotest.c b/testsuite/gpiotest/gpiotest.c index 65949ee..0bdd39f 100644 --- a/testsuite/gpiotest/gpiotest.c +++ b/testsuite/gpiotest/gpiotest.c @@ -1,19 +1,24 @@ /* - * Copyright (C) 2016 Philippe Gerum <r...@xenomai.org>. + * Copyright (C) 2016 Philippe Gerum <r...@xenomai.org> * - * 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 2 of the - * License, or (at your option) any later version. + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: * - * 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include <stdio.h> #include <error.h> diff --git a/testsuite/spitest/Makefile.am b/testsuite/spitest/Makefile.am new file mode 100644 index 0000000..ccdd392 --- /dev/null +++ b/testsuite/spitest/Makefile.am @@ -0,0 +1,19 @@ +testdir = @XENO_TEST_DIR@ + +CCLD = $(top_srcdir)/scripts/wrap-link.sh $(CC) + +test_PROGRAMS = spitest + +spitest_SOURCES = spitest.c + +spitest_CPPFLAGS = \ + $(XENO_USER_CFLAGS) \ + -I$(top_srcdir)/include + +spitest_LDFLAGS = @XENO_AUTOINIT_LDFLAGS@ $(XENO_POSIX_WRAPPERS) + +spitest_LDADD = \ + ../../lib/smokey/libsmokey.la \ + ../../lib/@XENO_CORE_LIB@ \ + @XENO_USER_LDADD@ \ + -lpthread -lrt diff --git a/testsuite/spitest/spitest.c b/testsuite/spitest/spitest.c new file mode 100644 index 0000000..0609bad --- /dev/null +++ b/testsuite/spitest/spitest.c @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2016 Philippe Gerum <r...@xenomai.org> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <sys/timerfd.h> +#include <stdio.h> +#include <error.h> +#include <semaphore.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <smokey/smokey.h> +#include <linux/spi/spidev.h> +#include <rtdm/spi.h> + +smokey_test_plugin(spi_transfer, + SMOKEY_ARGLIST( + SMOKEY_STRING(device), + SMOKEY_INT(speed), + SMOKEY_BOOL(latency), + ), + "Run a SPI transfer.\n" + "\tdevice=<device-path>\n" + "\tspeed=<speed-hz>\n" + "\tlatency" +); + +#define ONE_BILLION 1000000000 +#define TEN_MILLIONS 10000000 + +static int with_traffic = 1, with_latency; + +#define SEQ_SHIFT 24 +#define SEQ_MASK ((1 << SEQ_SHIFT) - 1) + +#define BAD_CRC 0x1 +#define BAD_SEQ 0x2 + +struct frame_header { + unsigned int seq: SEQ_SHIFT, + crc : 8; +} __attribute__((packed)); + +/* We send a 32bit header followed by 32 bytes of payload. */ +#define TRANSFER_SIZE (32 + sizeof(struct frame_header)) + +static unsigned char *i_area, *o_area; + +static unsigned int seq_out; + +static unsigned int seq_in = 1 << SEQ_SHIFT; + +static int32_t minjitter, maxjitter, avgjitter; +static int32_t gminjitter = TEN_MILLIONS, gmaxjitter = -TEN_MILLIONS; +static uint32_t goverrun, gerrors; +static int64_t gavgjitter; + +nanosecs_rel_t period_ns = ONE_BILLION / 2; /* 0.5s */ + +pthread_t display_tid, consumer_tid; + +static sem_t display_sem; + +static const int data_lines = 21; + +static inline void *get_obuf(void) +{ + return o_area; +} + +static inline void *get_odata(void) +{ + return o_area + sizeof(struct frame_header); +} + +static inline size_t get_odlen(void) +{ + return TRANSFER_SIZE - sizeof(struct frame_header); +} + +static void set_output_header(void) +{ + struct frame_header *fh = get_obuf(); + unsigned char *odata = get_odata(); + size_t odlen = get_odlen(), n; + unsigned char csum; + + for (n = 0, csum = 0; n < odlen; n++) + csum += *odata++; + + fh->crc = ~csum; + fh->seq = seq_out; + seq_out = (seq_out + 1) & SEQ_MASK; +} + +static int check_input_header(void *ibuf, size_t ilen) +{ + struct frame_header *fh = ibuf; + unsigned char *idata = ibuf + sizeof(*fh); + size_t idlen = ilen - sizeof(*fh), n; + unsigned int seq_next; + unsigned char csum; + int checkval = 0; + + for (n = 0, csum = 0; n < idlen; n++) + csum += *idata++; + + if (fh->crc != (unsigned char)~csum) + checkval |= BAD_CRC; + + if (seq_in > SEQ_MASK) + seq_in = fh->seq; + else { + seq_next = (seq_in + 1) & SEQ_MASK; + if (fh->seq != seq_next) { + /* Try to resync. */ + seq_in = 1 << SEQ_SHIFT; + checkval |= BAD_SEQ; + } else + seq_in = seq_next; + } + + return checkval; +} + +static void do_traffic(int round, const void *ibuf, + size_t ilen, int checkval) +{ + const struct frame_header *fh = ibuf; + size_t idlen = ilen - sizeof(*fh), n; + const unsigned char *idata = ibuf + sizeof(*fh); + + printf("%.4d> seq=%u%s, crc=%.2X%s", + round, + fh->seq, checkval & BAD_SEQ ? "?" : "", + fh->crc, checkval & BAD_CRC ? "?" : ""); + + for (n = 0; n < idlen; n++) { + if ((n % 16) == 0) + printf("\n"); + printf("%.2X ", idata[n]); + } + printf("\n"); +} + +static int do_process(int round) +{ + size_t odlen, n, ilen = TRANSFER_SIZE; + unsigned char *odata, *ibuf = i_area; + int checkval; + + checkval = check_input_header(i_area, ilen); + + if (with_traffic) + do_traffic(round, ibuf, ilen, checkval); + + odata = get_odata(); + odlen = get_odlen(); + for (n = 0; n < odlen; n++) + odata[n] = odata[n] + 1 ?: 1; + + set_output_header(); + + return checkval ? -EPROTO : 0; +} + +static void timespec_add_ns(struct timespec *t, unsigned int ns) +{ + t->tv_nsec += ns; + if (t->tv_nsec >= ONE_BILLION) { + t->tv_nsec -= ONE_BILLION; + t->tv_sec++; + } +} + +static inline long long diff_ts(struct timespec *left, struct timespec *right) +{ + return (long long)(left->tv_sec - right->tv_sec) * ONE_BILLION + + left->tv_nsec - right->tv_nsec; +} + +static void *display_thread(void *arg) +{ + long minj, gminj, maxj, gmaxj, avgj; + time_t start, now, dt; + int ret, n = 0; + + sem_init(&display_sem, 0, 0); + + time(&start); + + for (;;) { + ret = sem_wait(&display_sem); + if (ret < 0) { + if (errno != EIDRM) + panic("sem_wait(), %s", symerror(errno)); + return NULL; + } + + if (smokey_verbose_mode < 1) + continue; + + minj = minjitter; + gminj = gminjitter; + avgj = avgjitter; + maxj = maxjitter; + gmaxj = gmaxjitter; + + if (data_lines && (n++ % data_lines) == 0) { + time(&now); + dt = now - start; + printf("RTT| %.2ld:%.2ld:%.2ld (%Ld us period)\n", + dt / 3600, (dt / 60) % 60, dt % 60, + period_ns / 1000); + printf("RTH|%11s|%11s|%11s|%8s|%8s|%11s|%11s\n", + "----lat min", "----lat avg", + "----lat max", "-overrun", "-errors", + "---lat best", "--lat worst"); + } + printf("RTD|%11.3f|%11.3f|%11.3f|%8d|%8d|%11.3f|%11.3f\n", + (double)minj / 1000, + (double)avgj / 1000, + (double)maxj / 1000, + goverrun, + gerrors, + (double)gminj / 1000, (double)gmaxj / 1000); + } + + return NULL; +} + +static void start_display_thread(void) +{ + struct sched_param param; + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedpolicy(&attr, SCHED_OTHER); + param.sched_priority = 0; + pthread_attr_setschedparam(&attr, ¶m); + pthread_create(&display_tid, &attr, display_thread, NULL); +} + +static inline +nanosecs_rel_t get_start_delay(void) +{ + return ((1000000000ULL + period_ns - 1) / period_ns) * period_ns; +} + +static int do_spi_loop(int fd) +{ + int ret, n, nsamples, loops = 0, tfd; + struct timespec now, start; + struct itimerspec its; + + memset(get_odata(), 0x1, get_odlen()); + set_output_header(); + + if (with_latency) { + nsamples = (long long)ONE_BILLION / period_ns; + start_display_thread(); + } else + nsamples = 1; + + tfd = timerfd_create(CLOCK_MONOTONIC, 0); + if (tfd < 0) + return -errno; + + clock_gettime(CLOCK_MONOTONIC, &start); + timespec_add_ns(&start, get_start_delay()); + its.it_value = start; + its.it_interval.tv_sec = period_ns / ONE_BILLION; + its.it_interval.tv_nsec = period_ns % ONE_BILLION; + ret = timerfd_settime(tfd, TFD_TIMER_ABSTIME, &its, NULL); + if (ret) + return -errno; + + for (;;) { + int32_t minj = TEN_MILLIONS, maxj = -TEN_MILLIONS, dt; + uint32_t overrun = 0, errors = 0; + uint64_t ticks; + int64_t sumj; + + loops++; + + for (n = sumj = 0; n < nsamples; n++) { + ret = read(tfd, &ticks, sizeof(ticks)); + if (ret < 0) + break; + clock_gettime(CLOCK_MONOTONIC, &start); + if (!__T(ret, ioctl(fd, SPI_RTIOC_TRANSFER))) + return ret; + if (with_latency) { + clock_gettime(CLOCK_MONOTONIC, &now); + dt = (int32_t)diff_ts(&now, &start); + if (dt > maxj) + maxj = dt; + if (dt < minj) + minj = dt; + sumj += dt; + } + + ret = do_process(loops); + if (ret) + errors++; + } + + if (with_latency) { + minjitter = minj; + if (minj < gminjitter) + gminjitter = minj; + maxjitter = maxj; + if (maxj > gmaxjitter) + gmaxjitter = maxj; + avgjitter = sumj / nsamples; + gavgjitter += avgjitter; + goverrun += overrun; + gerrors += errors; + sem_post(&display_sem); + } + } + + return 0; +} + +static int run_spi_transfer(struct smokey_test *t, int argc, char *const argv[]) +{ + int fd, ret, speed_hz = 60000000; + struct rtdm_spi_config config; + struct rtdm_spi_iobufs iobufs; + const char *device = NULL; + struct sched_param param; + void *p; + + smokey_parse_args(t, argc, argv); + + if (SMOKEY_ARG_ISSET(spi_transfer, latency) && + SMOKEY_ARG_BOOL(spi_transfer, latency)) { + with_latency = 1; + /* Disable traffic tracing when monitoring latency. */ + with_traffic = 0; + } + + if (SMOKEY_ARG_ISSET(spi_transfer, speed)) + speed_hz = SMOKEY_ARG_INT(spi_transfer, speed); + + if (!SMOKEY_ARG_ISSET(spi_transfer, device)) { + warning("missing device= specification"); + return -EINVAL; + } + + device = SMOKEY_ARG_STRING(spi_transfer, device); + fd = open(device, O_RDWR); + if (fd < 0) { + ret = -errno; + warning("cannot open device %s [%s]", + device, symerror(ret)); + return ret; + } + + iobufs.io_len = TRANSFER_SIZE; + if (!__T(ret, ioctl(fd, SPI_RTIOC_SET_IOBUFS, &iobufs))) + return ret; + + p = mmap(NULL, iobufs.io_len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (!__Fassert(p == MAP_FAILED)) + return -EINVAL; + + smokey_trace("input_area[%u..%u], output_area[%u..%u], mapping length=%u", + iobufs.i_offset, iobufs.i_offset + TRANSFER_SIZE - 1, + iobufs.o_offset, iobufs.o_offset + TRANSFER_SIZE - 1, + iobufs.io_len); + + i_area = p + iobufs.i_offset; + o_area = p + iobufs.o_offset; + + config.mode = SPI_MODE_0; + config.bits_per_word = 8; + config.speed_hz = speed_hz; + if (!__T(ret, ioctl(fd, SPI_RTIOC_SET_CONFIG, &config))) + return ret; + + if (!__T(ret, ioctl(fd, SPI_RTIOC_GET_CONFIG, &config))) + return ret; + + smokey_trace("speed=%u hz, mode=%#x, bits=%u", + config.speed_hz, config.mode, config.bits_per_word); + + /* Switch current thread to real-time. */ + param.sched_priority = 10; + if (!__T(ret, pthread_setschedparam(pthread_self(), + SCHED_FIFO, ¶m))) + return ret; + + if (!__T(ret, do_spi_loop(fd))) + return ret; + + return 0; +} + +int main(int argc, char *const argv[]) +{ + struct smokey_test *t; + int ret, fails = 0; + + if (pvlist_empty(&smokey_test_list)) + return 0; + + for_each_smokey_test(t) { + ret = t->run(t, argc, argv); + if (ret) { + if (ret == -ENOSYS) { + smokey_note("%s skipped (no kernel support)", + t->name); + continue; + } + fails++; + if (smokey_keep_going) + continue; + if (smokey_verbose_mode) + error(1, -ret, "test %s failed", t->name); + return 1; + } + smokey_note("%s OK", t->name); + } + + return fails != 0; +} _______________________________________________ Xenomai-git mailing list Xenomai-git@xenomai.org https://xenomai.org/mailman/listinfo/xenomai-git