aboutsummaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
authorHarald Welte <laforge@osmocom.org>2020-01-04 22:04:28 +0100
committerHarald Welte <laforge@osmocom.org>2020-01-06 17:37:07 +0100
commiteb995b4a7e184e33cb67dad8b1efee3109f48f90 (patch)
treed794482648fdf45200281ffdaa5d78c0be23c19f /contrib
parent785476901c0d78960e7bcd82fcc2c5535fadd70c (diff)
contrib: add e1-prbs-test
e1-prbs-test is a small utility that can be used to do PRBS testing on E1/T1 lines using DAHDI cards. A transmiter and receiver are exchanging timeslot-specific PRBS sequences. Change-Id: Ib25d266e61e0d70919cc4e65d5b1bf0bc9ec7d00
Diffstat (limited to 'contrib')
-rw-r--r--contrib/e1-prbs-test/Makefile16
-rw-r--r--contrib/e1-prbs-test/README36
-rw-r--r--contrib/e1-prbs-test/internal.h65
-rw-r--r--contrib/e1-prbs-test/main.c208
-rw-r--r--contrib/e1-prbs-test/prbs.c183
-rw-r--r--contrib/e1-prbs-test/rx.c106
-rw-r--r--contrib/e1-prbs-test/tx.c47
-rw-r--r--contrib/e1-prbs-test/utils.c66
8 files changed, 727 insertions, 0 deletions
diff --git a/contrib/e1-prbs-test/Makefile b/contrib/e1-prbs-test/Makefile
new file mode 100644
index 0000000..726befc
--- /dev/null
+++ b/contrib/e1-prbs-test/Makefile
@@ -0,0 +1,16 @@
+LIBOSMO_CFLAGS:=$(shell pkg-config --cflags libosmocore)
+LIBOSMO_LIBS:=$(shell pkg-config --libs libosmocore)
+
+CFLAGS=-O2 -g -Wall -Werror $(LIBOSMO_CFLAGS)
+LIBS=$(LIBOSMO_LIBS)
+
+all: e1-prbs-test
+
+e1-prbs-test: main.o rx.o tx.o prbs.o utils.o
+ $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+%.o: %.c
+ $(CC) $(CFLAGS) -o $@ -c $^
+
+clean:
+ @rm -f e1-prbs-test e1-prbs-test *.o
diff --git a/contrib/e1-prbs-test/README b/contrib/e1-prbs-test/README
new file mode 100644
index 0000000..903cc39
--- /dev/null
+++ b/contrib/e1-prbs-test/README
@@ -0,0 +1,36 @@
+e1-prbs-test - Utility to test for bit errors on E1 lines using DAHDI
+======================================================================
+
+e1-prbs-test can be used to test for bit errors in E1 transmission
+lines. It consists of a sender and a receiver, which should be used
+on either end of the E1 line.
+
+Transmitter and receiver can be on the same machine, or on different
+machines.
+
+The code currently works directly on DAHDI, so only DAHDI-supported E1
+cards are supported at this point.
+
+The test works by sending timeslot-specific PRBS sequences of 512 bit
+(64byte) length on the transmit side, and by correlating to those PRBS
+sequences on the receiver side.
+
+The use is relatively simple:
+
+For the A-side, assuming you would want to use DAHDI span 1:
+ e1-prbs-test /dev/dahdi/chan/001
+
+For the B-side, assuming you would want to use DAHDI span 2:
+ e1-prbs-test /dev/dahdi/chan/002
+
+The test will run indefinitely.
+
+If you'd like to get an interim report, send a SIGHUP to
+e1-prbs-test.
+
+If you'd like to stop, simply press Ctrl+C.
+There is a two-stage shut-down process. When you press Ctrl+C for
+the first time, the report is printed, but transmission continues. At
+the second Ctrl+C, the process terminates. You must press Ctrl+C for
+the first time on both A and B side, before pressing it the second time
+on the A-side in order to get correct results.
diff --git a/contrib/e1-prbs-test/internal.h b/contrib/e1-prbs-test/internal.h
new file mode 100644
index 0000000..681648a
--- /dev/null
+++ b/contrib/e1-prbs-test/internal.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/prbs.h>
+#include <osmocom/core/select.h>
+
+#define MAX_NR_TS 31
+#define PRBS_LEN 2048
+
+/* prbs.c */
+
+struct timeslot_state;
+struct prbs_precomp {
+ uint8_t bytes[PRBS_LEN/8];
+};
+
+void prbs_for_ts_nr(struct osmo_prbs *prbs, uint8_t ts_nr);
+
+void prbs_precomp(struct prbs_precomp *out, const struct osmo_prbs *prbs);
+void ts_init_prbs_tx(struct timeslot_state *ts, unsigned int prbs_offs_tx);
+void ts_init_prbs_rx(struct timeslot_state *ts, unsigned int prbs_offs_rx);
+
+/* utils.c */
+uint8_t bits_set_in_byte(uint8_t byte);
+void cfg_dahdi_buffer(int fd);
+void set_realtime(int rt_prio);
+
+
+struct timeslot_state_tx {
+ struct osmo_prbs prbs; /* PRBS definition */
+ struct prbs_precomp prbs_pc; /* pre-computed PRBS bytes */
+ unsigned int prbs_pc_idx; /* next to-be-transmitted byte offset in prbs_pc */
+};
+
+struct timeslot_state_rx {
+ struct osmo_prbs prbs; /* PRBS definition */
+ struct prbs_precomp prbs_pc[8]; /* bit-shifted pre-computed PRBS sequences */
+ struct {
+ bool has_sync; /* do we have a PRBS sync? */
+ struct timespec ts_sync; /* time at which sync was established */
+ unsigned int prbs_pc_num; /* index to prbs_pc[] array */
+ unsigned int prbs_pc_offset; /* offset of next byte into prbs_pc[pc_num].bytes[] */
+
+ unsigned int num_bit_err; /* bit errors since last sync */
+ unsigned int num_sync_loss; /* number of sync losses since start */
+ } sync_state;
+};
+
+
+struct timeslot_state {
+ struct osmo_fd ofd;
+ struct timeslot_state_tx tx;
+ struct timeslot_state_rx rx;
+};
+
+struct test_state {
+ struct timeslot_state ts[MAX_NR_TS];
+ unsigned int next_unused_ts;
+};
+
+/* rx.c */
+void process_rx(struct timeslot_state_rx *tsr, unsigned int ts_nr, const uint8_t *data, unsigned int len);
+
+/* tx.c */
+void process_tx(struct timeslot_state *ts, int len);
diff --git a/contrib/e1-prbs-test/main.c b/contrib/e1-prbs-test/main.c
new file mode 100644
index 0000000..4fe0e84
--- /dev/null
+++ b/contrib/e1-prbs-test/main.c
@@ -0,0 +1,208 @@
+/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * 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.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <signal.h>
+#include <dahdi/user.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/prbs.h>
+
+#include "internal.h"
+
+static struct test_state g_tst;
+static int g_prbs_offs_rx;
+static int g_prbs_offs_tx;
+
+static int e1_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct timeslot_state *ts = ofd->data;
+ uint8_t buf[4096];
+ int rc, len;
+
+ OSMO_ASSERT(what & OSMO_FD_READ);
+
+ /* read whatever data */
+ rc = read(ofd->fd, buf, sizeof(buf));
+ if (rc < 0) {
+ fprintf(stderr, "E1TS(%d) read: %d (%s)\n", ofd->priv_nr, rc, strerror(errno));
+ return rc;
+ }
+ len = rc;
+ process_rx(&ts->rx, ofd->priv_nr, buf, len);
+
+ /* generate as many bytes as were read */
+ process_tx(ts, len);
+
+ return 0;
+}
+
+static int open_slots(struct test_state *tst, const char *basedir)
+{
+ DIR *dir = opendir(basedir);
+ struct dirent *ent;
+ int rc, num_slots = 0;
+
+ if (!dir)
+ return -ENOENT;
+
+ while ((ent = readdir(dir))) {
+ struct timeslot_state *ts;
+ switch (ent->d_type) {
+ case DT_CHR:
+ case DT_FIFO:
+ case DT_SOCK:
+ break;
+ default:
+ printf("%s: skipping\n", ent->d_name);
+ continue;
+ }
+
+ rc = openat(dirfd(dir), ent->d_name, O_RDWR);
+ if (rc < 0) {
+ fprintf(stderr, "Error opening %s: %d (%s)\n", ent->d_name, rc, strerror(errno));
+ return -1;
+ }
+ ts = &tst->ts[tst->next_unused_ts++];
+
+ /* open the respective file descriptor */
+ osmo_fd_setup(&ts->ofd, rc, BSC_FD_READ, e1_fd_cb, ts, atoi(ent->d_name));
+ osmo_fd_register(&ts->ofd);
+ printf("E1TS(%02u) opened\n", ts->ofd.priv_nr);
+
+ ts_init_prbs_tx(ts, g_prbs_offs_tx);
+ ts_init_prbs_rx(ts, g_prbs_offs_rx);
+
+ /* start to put something into the transmit queue, before we get read-triggered
+ * later on */
+ process_tx(ts, 1024);
+
+ cfg_dahdi_buffer(ts->ofd.fd);
+ struct dahdi_bufferinfo bi;
+ rc = ioctl(ts->ofd.fd, DAHDI_GET_BUFINFO, &bi);
+ OSMO_ASSERT(rc == 0);
+ printf("tx_pol=%d, rx_pol=%d, num=%d, size=%d, nread=%d, nwrite=%d\n",
+ bi.txbufpolicy, bi.rxbufpolicy, bi.numbufs, bi.bufsize, bi.readbufs, bi.writebufs);
+ num_slots++;
+ }
+ closedir(dir);
+ return num_slots;
+}
+
+static void print_report(void)
+{
+ struct timespec ts_now;
+ int i;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts_now);
+
+ for (i = 0; i < ARRAY_SIZE(g_tst.ts); i++) {
+ const struct timeslot_state *ts = &g_tst.ts[i];
+ printf("E1TS(%02u) STATS: sync_losses=%u, bit_errs=%u in %lu seconds\n",
+ ts->ofd.priv_nr, ts->rx.sync_state.num_sync_loss, ts->rx.sync_state.num_bit_err,
+ ts_now.tv_sec - ts->rx.sync_state.ts_sync.tv_sec);
+ }
+}
+
+static int g_ctrlc_count = 0;
+
+static void sig_handler(int signal)
+{
+ switch (signal) {
+ case SIGINT:
+ g_ctrlc_count++;
+ if (g_ctrlc_count == 1) {
+ print_report();
+ printf("\nPlease stop remote end before pressing Ctrl+C another time\n");
+ }
+ if (g_ctrlc_count > 1)
+ exit(0);
+ break;
+ case SIGHUP:
+ print_report();
+ break;
+ }
+}
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int c;
+ static const struct option long_opts[] = {
+ { "rx-prbs-offset", 1, 0, 'r' },
+ { "tx-prbs-offset", 1, 0, 't' },
+ { 0, 0, 0, 0 }
+ };
+ c = getopt_long(argc, argv, "r:t:", long_opts, NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'r':
+ g_prbs_offs_rx = atoi(optarg);
+ break;
+ case 't':
+ g_prbs_offs_tx = atoi(optarg);
+ break;
+ default:
+ exit(1);
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ char *basedir;
+ int rc;
+
+ handle_options(argc, argv);
+
+ if (argc <= optind) {
+ fprintf(stderr, "You must specify the base-path of your DAHDI span "
+ "like /dev/dahdi/chan/001\n");
+ exit(1);
+ }
+ basedir = argv[optind];
+
+ set_realtime(10);
+ rc = open_slots(&g_tst, basedir);
+ printf("==> opened a total of %d slots\n", rc);
+
+ signal(SIGINT, sig_handler);
+ signal(SIGHUP, sig_handler);
+ while (1) {
+ osmo_select_main(0);
+ }
+}
diff --git a/contrib/e1-prbs-test/prbs.c b/contrib/e1-prbs-test/prbs.c
new file mode 100644
index 0000000..ed8a80a
--- /dev/null
+++ b/contrib/e1-prbs-test/prbs.c
@@ -0,0 +1,183 @@
+/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * 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.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/prbs.h>
+
+#include "internal.h"
+
+/* according to https://users.ece.cmu.edu/~koopman/lfsr/index.html all below
+ * coefficients should render maximal length LFSRs of 11bit (2048) length */
+static const uint32_t prbs11_coeff[] = {
+ 0x402,
+ 0x40B,
+ 0x415,
+ 0x416,
+ 0x423,
+ 0x431,
+ 0x432,
+ 0x438,
+ 0x43D,
+ 0x446,
+ 0x44A,
+ 0x44F,
+ 0x454,
+ 0x458,
+ 0x467,
+ 0x468,
+ 0x470,
+ 0x473,
+ 0x475,
+ 0x47A,
+ 0x486,
+ 0x489,
+ 0x492,
+ 0x494,
+ 0x49D,
+ 0x49E,
+ 0x4A2,
+ 0x4A4,
+ 0x4A8,
+ 0x4AD,
+ 0x4B9,
+ 0x4BA,
+ 0x4BF,
+ 0x4C1,
+ 0x4C7,
+ 0x4D5,
+ 0x4D6,
+ 0x4DC,
+ 0x4E3,
+ 0x4EC,
+ 0x4F2,
+ 0x4FB,
+ 0x500,
+ 0x503,
+ 0x509,
+ 0x50A,
+ 0x514,
+ 0x524,
+ 0x530,
+ 0x536,
+ 0x53C,
+ 0x53F,
+ 0x542,
+ 0x548,
+ 0x54E,
+ 0x553,
+ 0x555,
+ 0x559,
+ 0x55A,
+ 0x56A,
+ 0x56F,
+ 0x574,
+ 0x577,
+ 0x578,
+ 0x57D,
+ 0x581,
+ 0x584,
+ 0x588,
+ 0x599,
+ 0x59F,
+ 0x5A0,
+ 0x5A5,
+ 0x5AC,
+ 0x5AF,
+ 0x5B2,
+ 0x5B7,
+ 0x5BE,
+ 0x5C3,
+ 0x5C5,
+ 0x5C9,
+ 0x5CA,
+ 0x5D7,
+ 0x5DB,
+ 0x5DE,
+ 0x5E4,
+ 0x5ED,
+ 0x5EE,
+ 0x5F3,
+ 0x5F6,
+ 0x605,
+ 0x606,
+ 0x60C,
+ 0x60F,
+ 0x62B,
+ 0x630,
+ 0x635,
+ 0x639,
+ 0x642,
+ 0x644,
+ 0x64B
+};
+
+/* build the PRBS description for a given timeslot number */
+void prbs_for_ts_nr(struct osmo_prbs *prbs, uint8_t ts_nr)
+{
+
+ OSMO_ASSERT(ts_nr < ARRAY_SIZE(prbs11_coeff));
+ prbs->name = "custom";
+ prbs->len = 11;
+ prbs->coeff = prbs11_coeff[ts_nr];
+}
+
+/* compute one full sequence of the given PRBS */
+void prbs_precomp(struct prbs_precomp *out, const struct osmo_prbs *prbs)
+{
+ struct osmo_prbs_state prbs_s;
+ int i;
+
+ osmo_prbs_state_init(&prbs_s, prbs);
+ for (i = 0; i < sizeof(out->bytes); i++) {
+ ubit_t ubit[8];
+ osmo_prbs_get_ubits(ubit, sizeof(ubit), &prbs_s);
+ osmo_ubit2pbit(&out->bytes[i], ubit, sizeof(ubit));
+ }
+}
+
+void ts_init_prbs_tx(struct timeslot_state *ts, unsigned int prbs_offs_tx)
+{
+ unsigned int prbs_nr = prbs_offs_tx + ts->ofd.priv_nr;
+ /* initialize the transmit-side PRNG for this slot */
+ printf("Selecting PRBS11 #%02u for Tx of TS%02u\n", prbs_nr, ts->ofd.priv_nr);
+ prbs_for_ts_nr(&ts->tx.prbs, prbs_nr);
+ prbs_precomp(&ts->tx.prbs_pc, &ts->tx.prbs);
+}
+
+void ts_init_prbs_rx(struct timeslot_state *ts, unsigned int prbs_offs_rx)
+{
+ unsigned int prbs_nr = prbs_offs_rx + ts->ofd.priv_nr;
+ /* initialize the receive-side PRNG for this slot */
+ ubit_t ubit[PRBS_LEN*2];
+ printf("Selecting PRBS11 #%02u for Rx of TS%02u\n", prbs_nr, ts->ofd.priv_nr);
+ prbs_for_ts_nr(&ts->rx.prbs, prbs_nr);
+ prbs_precomp(&ts->rx.prbs_pc[0], &ts->rx.prbs);
+ osmo_pbit2ubit(ubit, ts->rx.prbs_pc[0].bytes, PRBS_LEN);
+ /* copy buffer twice back-to-back */
+ memcpy(ubit+PRBS_LEN, ubit, PRBS_LEN);
+
+ /* pre-compute bit-shifted versions */
+ for (int i = 1; i < ARRAY_SIZE(ts->rx.prbs_pc); i++) {
+ osmo_ubit2pbit_ext(ts->rx.prbs_pc[i].bytes, 0, ubit, i, PRBS_LEN, 0);
+ //printf("%d: %s\n", i, osmo_hexdump_nospc(ts->prbs_pc[i].bytes, sizeof(ts->prbs_pc[i].bytes)));
+ }
+}
diff --git a/contrib/e1-prbs-test/rx.c b/contrib/e1-prbs-test/rx.c
new file mode 100644
index 0000000..bb6967d
--- /dev/null
+++ b/contrib/e1-prbs-test/rx.c
@@ -0,0 +1,106 @@
+/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * 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.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/utils.h>
+
+#include "internal.h"
+
+static uint8_t next_prbs_pc_byte(struct timeslot_state_rx *tsr)
+{
+ const struct prbs_precomp *pc = &tsr->prbs_pc[tsr->sync_state.prbs_pc_num];
+ uint8_t ret = pc->bytes[tsr->sync_state.prbs_pc_offset];
+ tsr->sync_state.prbs_pc_offset = (tsr->sync_state.prbs_pc_offset + 1) % sizeof(pc->bytes);
+ return ret;
+}
+
+/* compare if received buffer matches PRBS; count number of different bits */
+static unsigned int compare_buf(struct timeslot_state_rx *tsr, const uint8_t *data, unsigned int len)
+{
+ unsigned int i, num_wrong_bits = 0;
+
+ for (i = 0; i < len; i++) {
+ uint8_t bt = next_prbs_pc_byte(tsr);
+ if (data[i] != bt) {
+ uint8_t x = data[i] ^ bt;
+ num_wrong_bits += bits_set_in_byte(x);
+ }
+ }
+ return num_wrong_bits;
+}
+
+/* process incoming received data; try to correlate with prbs sequence */
+void process_rx(struct timeslot_state_rx *tsr, unsigned int ts_nr, const uint8_t *data, unsigned int len)
+{
+ if (!tsr->sync_state.has_sync) {
+ unsigned int pc_num;
+ /* we haven't synced yet and must attempt to sync to the pattern. We will try
+ * to match each pattern */
+ for (pc_num = 0; pc_num < ARRAY_SIZE(tsr->prbs_pc); pc_num++) {
+ const struct prbs_precomp *pc = &tsr->prbs_pc[pc_num];
+ uint8_t *found;
+ long int offset;
+
+ OSMO_ASSERT(len > sizeof(pc->bytes));
+ found = memmem(data, len, pc->bytes, sizeof(pc->bytes));
+ if (!found)
+ continue;
+
+ offset = (found - data);
+ printf("E1TS(%02u) FOUND SYNC (pc_num=%u, offset=%li)\n", ts_nr,
+ pc_num, offset);
+ clock_gettime(CLOCK_MONOTONIC, &tsr->sync_state.ts_sync);
+ tsr->sync_state.has_sync = true;
+ tsr->sync_state.prbs_pc_num = pc_num;
+ tsr->sync_state.prbs_pc_offset = (sizeof(pc->bytes) - offset) % sizeof(pc->bytes);
+ tsr->sync_state.num_bit_err = 0;
+ /* FIXME: compare the remainder of the buffer */
+ return;
+ }
+ }
+ if (tsr->sync_state.has_sync) {
+ unsigned int num_wrong_bits;
+ /* we already have sync */
+ num_wrong_bits = compare_buf(tsr, data, len);
+ if (num_wrong_bits >= len*8/4) { /* more than 25% of wrong bits */
+ struct timespec ts_now;
+ clock_gettime(CLOCK_MONOTONIC, &ts_now);
+ printf("E1TS(%02u) LOST SYNC after %u of %u wrong bits in one buffer; "
+ "until now, total bit errors %u in %lu seconds\n", ts_nr,
+ num_wrong_bits, len*8, tsr->sync_state.num_bit_err,
+ ts_now.tv_sec - tsr->sync_state.ts_sync.tv_sec);
+ tsr->sync_state.has_sync = false;
+ tsr->sync_state.num_sync_loss++;
+ }
+ tsr->sync_state.num_bit_err += num_wrong_bits;
+ }
+}
diff --git a/contrib/e1-prbs-test/tx.c b/contrib/e1-prbs-test/tx.c
new file mode 100644
index 0000000..6d775d7
--- /dev/null
+++ b/contrib/e1-prbs-test/tx.c
@@ -0,0 +1,47 @@
+/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * 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.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/utils.h>
+
+#include "internal.h"
+
+void process_tx(struct timeslot_state *ts, int len)
+{
+ uint8_t buf[4096];
+ int i, rc;
+
+ for (i = 0; i < len; i++) {
+ buf[i] = ts->tx.prbs_pc.bytes[ts->tx.prbs_pc_idx];
+ ts->tx.prbs_pc_idx = (ts->tx.prbs_pc_idx + 1) % sizeof(ts->tx.prbs_pc);
+ }
+ rc = write(ts->ofd.fd, buf, len);
+ if (rc != len)
+ fprintf(stderr, "E1TS(%02u) write: %d bytes less than %d\n", ts->ofd.priv_nr, rc, len);
+}
diff --git a/contrib/e1-prbs-test/utils.c b/contrib/e1-prbs-test/utils.c
new file mode 100644
index 0000000..5610e80
--- /dev/null
+++ b/contrib/e1-prbs-test/utils.c
@@ -0,0 +1,66 @@
+/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * 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.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <sched.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <dahdi/user.h>
+
+#include <osmocom/core/utils.h>
+
+/* we could generate a lookup table at start ... */
+uint8_t bits_set_in_byte(uint8_t byte)
+{
+ uint8_t ret = 0;
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ if (byte & (1 << i))
+ ret += 1;
+ }
+ return ret;
+}
+
+void cfg_dahdi_buffer(int fd)
+{
+ struct dahdi_bufferinfo bi = {
+ .txbufpolicy = DAHDI_POLICY_WHEN_FULL, /* default is immediate */
+ .rxbufpolicy = DAHDI_POLICY_WHEN_FULL, /* default is immediate */
+ .numbufs = 8, /* default is 2 */
+ .bufsize = 1024, /* default is 1024 */
+ .readbufs = -1,
+ .writebufs = -1,
+ };
+ OSMO_ASSERT(ioctl(fd, DAHDI_SET_BUFINFO, &bi) == 0);
+}
+
+void set_realtime(int rt_prio)
+{
+ struct sched_param param;
+ int rc;
+
+ memset(&param, 0, sizeof(param));
+ param.sched_priority = rt_prio;
+ rc = sched_setscheduler(getpid(), SCHED_RR, &param);
+ OSMO_ASSERT(rc == 0);
+}