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
This commit is contained in:
Harald Welte 2020-01-04 22:04:28 +01:00
parent 785476901c
commit eb995b4a7e
8 changed files with 727 additions and 0 deletions

View File

@ -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

View File

@ -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.

View File

@ -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);

208
contrib/e1-prbs-test/main.c Normal file
View File

@ -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);
}
}

183
contrib/e1-prbs-test/prbs.c Normal file
View File

@ -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)));
}
}

106
contrib/e1-prbs-test/rx.c Normal file
View File

@ -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;
}
}

47
contrib/e1-prbs-test/tx.c Normal file
View File

@ -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);
}

View File

@ -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);
}