import latest e1-tracer host software

This was originally in https://github.com/smunaut/ice40-playground
but has meanwhile been removed, only a fork at
https://github.com/laf0rge/ice40-playground remained.

Let's import the code here.  The history is not all that exciting
so I'm saving myself the effort of git filter-branch or the like
to import the actual old commits.

The commit log is here for reference:

commit bd36d40cc30acbae68978df79eff03f0539a9a21
Author: Harald Welte <laforge@osmocom.org>
Date:   Fri Sep 4 10:21:51 2020 +0200

    e1-recorder main.c: Don't exist just because one ISO transfer failed

    I get LIBUSB_TRANSFER_ERROR every so often (once every few hours) on my
    laptop.  Sure, it likely means there was some data lost, but the trace
    can continue juts fine after this error.  So make it non-fatal and
    continue running.

commit 9b02b3e87400ba3d27bff05e2632fbd5a0698e8c
Author: Harald Welte <laforge@osmocom.org>
Date:   Mon Aug 31 10:01:20 2020 +0200

    Add 'replay' to re-play 2Mbps stream with realistic speed (to stdout)

commit 23103d3a8f9cc8b3f0ba602dbb4240040b2d008a
Author: Harald Welte <laforge@osmocom.org>
Date:   Sun Aug 30 15:41:12 2020 +0200

    dump: Use osmo_e1f to decode; send HLDC over GSMTAP

commit 7acd063a1245a34835d4488be3caad592f3c19d5
Author: Harald Welte <laforge@osmocom.org>
Date:   Thu Aug 20 16:48:15 2020 +0200

    dump.c: Add error plotting support

commit 90b90d04fff494b70ad220bd82ac48d43f3096c9
Author: Harald Welte <laforge@osmocom.org>
Date:   Mon Jan 13 18:20:39 2020 +0100

    dump: Add support for writing LAPD PCAP of E1 superchannel

commit f3a0cc34f0901c2cf257367351dddfddc6f7301e
Author: Harald Welte <laforge@osmocom.org>
Date:   Mon Jan 13 16:37:23 2020 +0100

    dump.c: Move handling of frames to separate function

commit 8deffb5907979e43e3a4236ea7bb8d54513513ac
Author: Harald Welte <laforge@osmocom.org>
Date:   Mon Jan 13 16:34:39 2020 +0100

    dump.c: remove local hexdump.[ch] and use libosmocore proper

commit 80d81e78737237ba3b6bae1deb7d5107d7f613b6
Author: Harald Welte <laforge@osmocom.org>
Date:   Tue Sep 1 13:52:52 2020 +0200

    tracer: use 0x6151 as PID

    See 5e68831b26

commit b219af27b34bbd6deb2419aa38852d8d227b9fc0
Author: Harald Welte <laforge@osmocom.org>
Date:   Mon Jan 6 13:47:28 2020 +0100

    e1-recorder: Don't silently ignore write failures

    The disk may be full, for example. We should report that to the user
    and terminate the program.

commit ead99fdfae6f1db54c79a91ef407a86c67352d87
Author: Harald Welte <laforge@osmocom.org>
Date:   Mon Jan 6 12:41:23 2020 +0100

    e1-recorder: Exit process on transfer + resubmit failure

    This is particularly importnat in case the USB device for some reason
    gets disconnected and re-connects.  We want the process to fail fast
    and have systemd respawn us.

commit a521e026c76056a60531c490fa2ef84e8e633c9a
Author: Harald Welte <laforge@osmocom.org>
Date:   Mon Jan 6 12:35:20 2020 +0100

    e1-recorder: use libusb_strerror() to decode error messages

commit 43a8fbdfdc35f5ba7564cfe5c92f43b124abf710
Author: Harald Welte <laforge@osmocom.org>
Date:   Sun Jan 5 19:49:58 2020 +0100

    e1-recorder: Acytually make '-r' (SCHED_RR) work

commit 84e28225d9ba1de9fb09660a96bd3b0b798c88d9
Author: Harald Welte <laforge@osmocom.org>
Date:   Sun Jan 5 17:53:11 2020 +0100

    e1-recorder: Add small 'dump' program

    This program will print one line for each E1 frame, where each line
    consists of 32 hex bytes: one for each timeslot

commit 0c697b40620b5d18480e626991dc639daadf58a6
Author: Sylvain Munaut <tnt@246tNt.com>
Date:   Sun Dec 29 13:13:17 2019 +0100

    projects/riscv_usb: Host software for the dual channel sniffer

    Signed-off-by: Sylvain Munaut <tnt@246tNt.com>

Closes: OS#5673
Change-Id: I9319c1eb8f822830307c1a181d357c58ce43efba
This commit is contained in:
Harald Welte 2022-10-30 22:20:55 +01:00
parent c756644205
commit f5e7264a27
16 changed files with 2405 additions and 0 deletions

View File

@ -1,2 +1,3 @@
--exclude ^software/e1-tracer/crc4itu.(c|h)$
--ignore OPEN_BRACE
--ignore SPACING

View File

@ -0,0 +1,14 @@
CC=gcc
CFLAGS=`pkg-config libusb-1.0 libosmocore libosmoabis --cflags` -O2 -Wall
LDLIBS=`pkg-config libusb-1.0 libosmocore --libs`
OBJS=main dump replay hdlc-decode-pipe
all: $(OBJS)
main: idt82v2081.o idt82v2081_usb.o main.o
dump: dump.o crc4itu.o osmo_e1f.o
clean:
rm -f $(OBJS) *.o

View File

@ -0,0 +1,56 @@
/**
* \file
* Functions and types for CRC checks.
*
* Generated on Sat May 12 09:39:22 2018
* by pycrc v0.9.1, https://pycrc.org
* using the configuration:
* - Width = 4
* - Poly = 0x3
* - XorIn = 0x0
* - ReflectIn = False
* - XorOut = 0x0
* - ReflectOut = False
* - Algorithm = table-driven
*/
#include "crc4itu.h" /* include the header file generated with pycrc */
#include <stdlib.h>
#include <stdint.h>
/**
* Static table used for the table_driven implementation.
*/
static const crc_t crc_table[256] = {
0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02,
0x05, 0x06, 0x03, 0x00, 0x09, 0x0a, 0x0f, 0x0c, 0x0e, 0x0d, 0x08, 0x0b, 0x02, 0x01, 0x04, 0x07,
0x0a, 0x09, 0x0c, 0x0f, 0x06, 0x05, 0x00, 0x03, 0x01, 0x02, 0x07, 0x04, 0x0d, 0x0e, 0x0b, 0x08,
0x0f, 0x0c, 0x09, 0x0a, 0x03, 0x00, 0x05, 0x06, 0x04, 0x07, 0x02, 0x01, 0x08, 0x0b, 0x0e, 0x0d,
0x07, 0x04, 0x01, 0x02, 0x0b, 0x08, 0x0d, 0x0e, 0x0c, 0x0f, 0x0a, 0x09, 0x00, 0x03, 0x06, 0x05,
0x02, 0x01, 0x04, 0x07, 0x0e, 0x0d, 0x08, 0x0b, 0x09, 0x0a, 0x0f, 0x0c, 0x05, 0x06, 0x03, 0x00,
0x0d, 0x0e, 0x0b, 0x08, 0x01, 0x02, 0x07, 0x04, 0x06, 0x05, 0x00, 0x03, 0x0a, 0x09, 0x0c, 0x0f,
0x08, 0x0b, 0x0e, 0x0d, 0x04, 0x07, 0x02, 0x01, 0x03, 0x00, 0x05, 0x06, 0x0f, 0x0c, 0x09, 0x0a,
0x0e, 0x0d, 0x08, 0x0b, 0x02, 0x01, 0x04, 0x07, 0x05, 0x06, 0x03, 0x00, 0x09, 0x0a, 0x0f, 0x0c,
0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09,
0x04, 0x07, 0x02, 0x01, 0x08, 0x0b, 0x0e, 0x0d, 0x0f, 0x0c, 0x09, 0x0a, 0x03, 0x00, 0x05, 0x06,
0x01, 0x02, 0x07, 0x04, 0x0d, 0x0e, 0x0b, 0x08, 0x0a, 0x09, 0x0c, 0x0f, 0x06, 0x05, 0x00, 0x03,
0x09, 0x0a, 0x0f, 0x0c, 0x05, 0x06, 0x03, 0x00, 0x02, 0x01, 0x04, 0x07, 0x0e, 0x0d, 0x08, 0x0b,
0x0c, 0x0f, 0x0a, 0x09, 0x00, 0x03, 0x06, 0x05, 0x07, 0x04, 0x01, 0x02, 0x0b, 0x08, 0x0d, 0x0e,
0x03, 0x00, 0x05, 0x06, 0x0f, 0x0c, 0x09, 0x0a, 0x08, 0x0b, 0x0e, 0x0d, 0x04, 0x07, 0x02, 0x01,
0x06, 0x05, 0x00, 0x03, 0x0a, 0x09, 0x0c, 0x0f, 0x0d, 0x0e, 0x0b, 0x08, 0x01, 0x02, 0x07, 0x04
};
crc_t crc4itu_update(crc_t crc, const void *data, size_t data_len)
{
const unsigned char *d = (const unsigned char *)data;
unsigned int tbl_idx;
while (data_len--) {
tbl_idx = (crc << 4) ^ *d;
crc = crc_table[tbl_idx] & 0xf;
d++;
}
return crc & 0xf;
}

View File

@ -0,0 +1,106 @@
/**
* \file
* Functions and types for CRC checks.
*
* Generated on Sat May 12 09:41:12 2018
* by pycrc v0.9.1, https://pycrc.org
* using the configuration:
* - Width = 4
* - Poly = 0x3
* - XorIn = 0x0
* - ReflectIn = False
* - XorOut = 0x0
* - ReflectOut = False
* - Algorithm = table-driven
*
* This file defines the functions crc4itu_init(), crc4itu_update() and crc_finalize().
*
* The crc4itu_init() function returns the initial \c crc value and must be called
* before the first call to crc4itu_update().
* Similarly, the crc_finalize() function must be called after the last call
* to crc4itu_update(), before the \c crc is being used.
* is being used.
*
* The crc4itu_update() function can be called any number of times (including zero
* times) in between the crc4itu_init() and crc_finalize() calls.
*
* This pseudo-code shows an example usage of the API:
* \code{.c}
* crc_t crc;
* unsigned char data[MAX_DATA_LEN];
* size_t data_len;
*
* crc = crc4itu_init();
* while ((data_len = read_data(data, MAX_DATA_LEN)) > 0) {
* crc = crc4itu_update(crc, data, data_len);
* }
* crc = crc_finalize(crc);
* \endcode
*/
#ifndef CRC4ITU_H
#define CRC4ITU_H
#include <stdlib.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* The definition of the used algorithm.
*
* This is not used anywhere in the generated code, but it may be used by the
* application code to call algorithm-specific code, if desired.
*/
#define CRC_ALGO_TABLE_DRIVEN 1
/**
* The type of the CRC values.
*
* This type must be big enough to contain at least 4 bits.
*/
typedef uint_fast8_t crc_t;
/**
* Calculate the initial crc value.
*
* \return The initial crc value.
*/
static inline crc_t crc4itu_init(void)
{
return 0x0;
}
/**
* Update the crc value with new data.
*
* \param[in] crc The current crc value.
* \param[in] data Pointer to a buffer of \a data_len bytes.
* \param[in] data_len Number of bytes in the \a data buffer.
* \return The updated crc value.
*/
crc_t crc4itu_update(crc_t crc, const void *data, size_t data_len);
/**
* Calculate the final crc value.
*
* \param[in] crc The current crc value.
* \return The final crc value.
*/
static inline crc_t crc_finalize(crc_t crc)
{
return crc;
}
#ifdef __cplusplus
} /* closing brace for extern "C" */
#endif
#endif /* CRC4ITU_H */

303
software/e1-tracer/dump.c Normal file
View File

@ -0,0 +1,303 @@
/* (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.
*/
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <inttypes.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/application.h>
#include <osmocom/core/isdnhdlc.h>
#include <osmocom/core/gsmtap.h>
#include <osmocom/core/gsmtap_util.h>
//#include <osmocom/abis/lapd_pcap.h>
#include "osmo_e1f.h"
#define E1_CHUNK_HDR_MAGIC 0xe115600d /* E1 is good */
struct e1_chunk_hdr {
uint32_t magic;
struct {
uint64_t sec;
uint64_t usec;
} time;
uint16_t len; /* length of following payload */
uint8_t ep; /* USB endpoint */
} __attribute__((packed));
/* local state per E1 line */
struct line_state {
unsigned int idx;
struct osmo_isdnhdlc_vars hdlc;
struct osmo_e1f_instance e1f;
struct {
FILE *outfile;
uint64_t last_sec;
uint64_t frame_err_cnt;
uint64_t crc_err_cnt;
bool ts0_crc4_error;
bool ts0_remote_alarm;
} errplot;
};
static struct line_state g_line[2]; /* one per direction */
static int g_pcap_fd = -1;
static struct msgb *g_pcap_msg;
struct gsmtap_inst *g_gsmtap;
/* called for each HDLC payload frame */
static void handle_payload(uint8_t idx, const uint8_t *data, int len)
{
#if 0
int dir;
switch (idx) {
case 0:
dir = OSMO_LAPD_PCAP_INPUT;
break;
case 1:
dir = OSMO_LAPD_PCAP_OUTPUT;
break;
default:
fprintf(stderr, "Unexpected USB EP idx %d\n", idx);
return;
}
if (g_pcap_fd >= 0) {
uint8_t *cur = msgb_put(g_pcap_msg, len);
memcpy(cur, data, len);
osmo_pcap_lapd_write(g_pcap_fd, dir, g_pcap_msg);
msgb_reset(g_pcap_msg);
} else
#endif
printf("%u: OUT: %s\n", idx, osmo_hexdump(data, len));
if (g_gsmtap) {
struct msgb *msg;
msg = gsmtap_makemsg_ex(GSMTAP_TYPE_E1T1, idx ? GSMTAP_ARFCN_F_UPLINK : 0, 255,
GSMTAP_E1T1_FR, 0, 0, 0, 0, data, len);
OSMO_ASSERT(msg);
gsmtap_sendmsg(g_gsmtap, msg);
}
}
static void handle_frame_errplot(struct line_state *ls, const struct e1_chunk_hdr *hdr, const uint8_t *data)
{
if (!ls->errplot.outfile)
return;
if (!ls->errplot.last_sec)
ls->errplot.last_sec = hdr->time.sec;
if (ls->errplot.last_sec != hdr->time.sec) {
/* dump the per-second total; start from 0 again */
fprintf(ls->errplot.outfile, "%"PRIu64 " %"PRIu64 " %"PRIu64" %u %u\n",
hdr->time.sec, ls->errplot.frame_err_cnt, ls->errplot.crc_err_cnt,
ls->errplot.ts0_crc4_error, ls->errplot.ts0_remote_alarm);
ls->errplot.frame_err_cnt = 0;
ls->errplot.crc_err_cnt = 0;
ls->errplot.ts0_remote_alarm = false;
ls->errplot.ts0_crc4_error = false;
ls->errplot.last_sec = hdr->time.sec;
fflush(ls->errplot.outfile);
}
}
/* called for each USB transfer read from the file */
static void handle_frame(const struct e1_chunk_hdr *hdr, const uint8_t *data)
{
uint8_t nots0[1024];
unsigned int offs = 0;
struct line_state *ls;
/* filter on the endpoint (direction) specified by the user */
switch (hdr->ep) {
case 0x81:
ls = &g_line[0];
break;
case 0x82:
ls = &g_line[1];
break;
default:
fprintf(stderr, "Unexpected USB EP 0x%02x\n", hdr->ep);
return;
}
if (hdr->len <= 4)
return;
//printf("%u: %"PRIu64".%"PRIu64" EP=0x%02x\n", ls->idx, hdr->time.sec, hdr->time.usec, hdr->ep);
OSMO_ASSERT(((hdr->len-4)/32)*31 < ARRAY_SIZE(nots0));
/* gather the TS1..TS31 data, skipping TS0 */
for (int i = 4; i < hdr->len-4; i += 32) {
//printf("%u:\t%s\n", ls->idx, osmo_hexdump(data+i, 32));
memcpy(nots0+offs, data+i+1, 32-1);
offs += 31;
osmo_e1f_rx_frame(&ls->e1f, data+i);
}
//printf("%u: IN(%u): %s\n", ls->idx, offs, osmo_hexdump(nots0, offs));
uint8_t out[2048];
int rc;
int rl;
int oi = 0;
while (oi < offs) {
rc = osmo_isdnhdlc_decode(&ls->hdlc, nots0+oi, offs-oi, &rl, out, sizeof(out));
//printf("%u: osmo_isdnhdlc_decode(hdlc, nots0+%d, inlen=%d, &rl=%d, out, %zu)=%d\n", ls->idx, oi, offs-oi, rl, sizeof(out), rc);
if (rc < 0) {
fprintf(stdout, "%u: ERR in HDLC decode: %d\n", ls->idx, rc);
if (rc == -1)
ls->errplot.frame_err_cnt++;
else if (rc == -2)
ls->errplot.crc_err_cnt++;
} else if (rc > 0)
handle_payload(ls->idx, out, rc);
oi += rl;
}
handle_frame_errplot(ls, hdr, data);
}
static int process_file(int fd)
{
struct e1_chunk_hdr hdr;
unsigned long offset = 0;
uint8_t buf[65535];
int rc;
while (1) {
memset(buf, 0, sizeof(buf));
/* first read header */
rc = read(fd, &hdr, sizeof(hdr));
if (rc < 0)
return rc;
if (rc != sizeof(hdr)) {
fprintf(stderr, "%d is less than header size (%zd)\n", rc, sizeof(hdr));
return -1;
}
offset += rc;
if (hdr.magic != E1_CHUNK_HDR_MAGIC) {
fprintf(stderr, "offset %lu: Wrong magic 0x%08x\n", offset, hdr.magic);
return -1;
}
/* then read payload */
rc = read(fd, buf, hdr.len);
if (rc < 0)
return rc;
offset += rc;
if (rc != hdr.len) {
fprintf(stderr, "%d is less than payload size (%d)\n", rc, hdr.len);
return -1;
}
handle_frame(&hdr, buf);
}
}
static int open_file(const char *fname)
{
return open(fname, O_RDONLY);
}
/* E1 framer notifies us of something */
static void notify_cb(struct osmo_e1f_instance *e1i, enum osmo_e1f_notify_event evt, bool present, void *data)
{
struct line_state *ls = e1i->priv;
printf("%u: NOTIFY: %s %s\n", ls->idx, osmo_e1f_notify_event_name(evt),
present ? "PRESENT" : "ABSENT");
if (present) {
switch (evt) {
case E1_NTFY_EVT_CRC_ERROR:
ls->errplot.ts0_crc4_error = present;
break;
case E1_NTFY_EVT_REMOTE_ALARM:
ls->errplot.ts0_remote_alarm = present;
break;
}
}
}
static const struct log_info_cat log_categories[] = {
};
static const struct log_info log_info = {
.cat = log_categories,
.num_cat = ARRAY_SIZE(log_categories),
};
int main(int argc, char **argv)
{
char *fname;
int rc;
int i;
osmo_init_logging2(NULL, &log_info);
osmo_e1f_init();
if (argc < 2) {
fprintf(stderr, "You must specify the file name of the ICE40-E1 capture\n");
exit(1);
}
fname = argv[1];
rc = open_file(fname);
if (rc < 0) {
fprintf(stderr, "Error opening %s: %s\n", fname, strerror(errno));
exit(1);
}
g_gsmtap = gsmtap_source_init("localhost", GSMTAP_UDP_PORT, 0);
gsmtap_source_add_sink(g_gsmtap);
if (argc >= 3) {
#if 0
g_pcap_fd = osmo_pcap_lapd_open(argv[2], 0640);
if (g_pcap_fd < 0) {
fprintf(stderr, "Unable to open PCAP output: %s\n", strerror(errno));
exit(1);
}
g_pcap_msg = msgb_alloc(4096, "pcap");
#endif
}
for (i = 0; i < ARRAY_SIZE(g_line); i++) {
struct line_state *ls = &g_line[i];
char namebuf[32];
ls->idx = i;
osmo_isdnhdlc_rcv_init(&ls->hdlc, OSMO_HDLC_F_BITREVERSE);
osmo_e1f_instance_init(&ls->e1f, "dump", &notify_cb, true, ls);
snprintf(namebuf, sizeof(namebuf), "errplot-%d.dat", i);
ls->errplot.outfile = fopen(namebuf, "w");
}
process_file(rc);
}

61
software/e1-tracer/dump_ts.py Executable file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env python3
import binascii
import struct
import sys
from collections import namedtuple
fh_out0 = open('/tmp/out0.xlaw', 'wb')
fh_out1 = open('/tmp/out1.xlaw', 'wb')
class Header(namedtuple('Header', 'magic ts_sec ts_usec len ep')):
sd = struct.Struct('=LQQhB')
size = sd.size
@classmethod
def unpack(kls, data):
return kls(*kls.sd.unpack(data))
def process_frame(ep, frame):
#print("%02x %s" % (ep, binascii.b2a_hex(frame).decode('utf-8')))
if False:
if ep == 0x81:
ts = 1
fh_out0.write(frame[ts:ts+1])
elif ep == 0x82:
ts = 1
fh_out1.write(frame[ts:ts+1])
with open(sys.argv[1], 'rb') as fh_in:
while True:
hdr_data = fh_in.read(Header.size)
if len(hdr_data) != Header.size:
break
hdr = Header.unpack(hdr_data)
if hdr.magic != 0xe115600d:
print("Bad header %r" % (hdr,))
break
if hdr.len < 0:
print("Error %r" % (hdr,))
continue
if hdr.len > 0:
data = fh_in.read(hdr.len)
print(hdr.ep, binascii.b2a_hex(data[0:4]).decode('utf-8'), hdr.len)
nf = (len(data) - 4) // 32
for i in range(nf):
process_frame(hdr.ep, data[4+32*i:4+32*(i+1)])
#print(hdr)

View File

@ -0,0 +1,55 @@
/* (C) 2020 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.
*/
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <inttypes.h>
#include <osmocom/core/isdnhdlc.h>
static struct osmo_isdnhdlc_vars g_hdlc;
/* read bitstream from STDIN, pass through HDLC decoder, write decoded frames to stdout */
int main(int argc, char **argv)
{
osmo_isdnhdlc_rcv_init(&g_hdlc, OSMO_HDLC_F_BITREVERSE);
while (1) {
uint8_t inbuf[320];
uint8_t outbuf[2048];
int rc, inlen, outlen;
rc = read(0, inbuf, sizeof(inbuf));
if (rc < 0)
exit(1);
else if (rc == 0)
exit(0);
inlen = rc;
rc = osmo_isdnhdlc_decode(&g_hdlc, inbuf, inlen, &outlen, outbuf, sizeof(outbuf));
if (outlen > 0)
write(1, outbuf, outlen);
}
}

View File

@ -0,0 +1,140 @@
/* (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.
*/
#include <stdint.h>
#include "idt82v2081.h"
#include "idt82v2081_regs.h"
/*! \brief Set or clear some (masked) bits inside a register
* \param[in] e4k reference to the tuner
* \param[in] reg number of the register
* \param[in] mask bit-mask of the value
* \param[in] val data value to be written to register
* \returns 0 on success, negative in case of error
*/
static int idt82_reg_set_bit_mask(struct idt82 *idt, uint8_t reg,
uint8_t mask, uint8_t val)
{
uint8_t tmp = idt82_reg_read(idt, reg);
if ((tmp & mask) == val)
return 0;
return idt82_reg_write(idt, reg, (tmp & ~mask) | (val & mask));
}
int idt82_termination(struct idt82 *idt, enum idt82_term term)
{
uint8_t puls, scal;
idt82_reg_set_bit_mask(idt, IDT_REG_TERM, term | (term << IDT_TERM_T_SHIFT),
IDT_TERM_T_MASK | IDT_TERM_R_MASK);
switch (idt->mode) {
case IDT_MODE_E1:
if (term == IDT_TERM_INT_75)
puls = 0;
else
puls = 1;
scal = 0x21;
break;
case IDT_MODE_T1:
/* FIXME: different length! */
puls = 2;
scal = 0x36;
break;
case IDT_MODE_J1:
puls = 7;
scal = 0x36;
break;
default:
return -1;
}
idt82_reg_set_bit_mask(idt, IDT_REG_TCF1, puls, IDT_TCF1_PULS_MASK);
idt82_reg_set_bit_mask(idt, IDT_REG_TCF2, scal, IDT_TCF2_SCAL_MASK);
idt->term = term;
return 0;
}
int idt82_mode(struct idt82 *idt, enum idt82_mode mode)
{
switch (mode) {
case IDT_MODE_E1:
idt82_reg_set_bit_mask(idt, IDT_REG_GCF, IDT_GCF_T1E1_E1,
IDT_GCF_T1E1_MASK);
break;
case IDT_MODE_T1:
case IDT_MODE_J1:
idt82_reg_set_bit_mask(idt, IDT_REG_GCF, IDT_GCF_T1E1_T1,
IDT_GCF_T1E1_MASK);
break;
}
idt->mode = mode;
return 0;
}
int idt82_get_errcount(struct idt82 *idt)
{
uint16_t ret;
int rc;
rc = idt82_reg_read(idt, IDT_REG_CNT0);
if (rc < 0)
return rc;
ret = rc;
rc = idt82_reg_read(idt, IDT_REG_CNT1);
if (rc < 0)
return rc;
ret |= (rc << 8);
return ret;
}
/* return in dB, range is return value ... (value + 2) */
int idt82_get_line_att(struct idt82 *idt)
{
int rc;
rc = idt82_reg_read(idt, IDT_REG_STAT1);
if (rc < 0)
return rc;
return (rc & IDT_STAT1_ATT_MASK)*2;
}
int idt82_init(struct idt82 *idt, bool monitor)
{
idt82_reg_write(idt, IDT_REG_RST, 0x00); /* Reset to defaults */
idt82_mode(idt, IDT_MODE_E1);
idt82_termination(idt, IDT_TERM_INT_120);
idt82_reg_write(idt, IDT_REG_TCF0, 0x10); /* Disable TX */
if (monitor)
idt82_reg_write(idt, IDT_REG_RCF2, 0x19); /* 22 dB monitor mode */
return 0;
}

View File

@ -0,0 +1,35 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
enum idt82_term {
IDT_TERM_INT_75 = 0,
IDT_TERM_INT_120,
IDT_TERM_INT_100,
IDT_TERM_INT_110,
IDT_TERM_EXT,
};
enum idt82_mode {
IDT_MODE_E1 = 0,
IDT_MODE_T1,
IDT_MODE_J1,
};
struct idt82 {
enum idt82_mode mode;
enum idt82_term term;
void *priv;
uint8_t cs;
};
int idt82_termination(struct idt82 *idt, enum idt82_term term);
int idt82_mode(struct idt82 *idt, enum idt82_mode mode);
int idt82_get_errcount(struct idt82 *idt);
int idt82_get_line_att(struct idt82 *idt);
int idt82_init(struct idt82 *idt, bool monitor);
/* callbacks into transport */
int idt82_reg_write(struct idt82 *idt, uint8_t reg, uint8_t val);
int idt82_reg_read(struct idt82 *idt, uint8_t reg);

View File

@ -0,0 +1,81 @@
#ifndef _IDT82_REGS_H
#define _IDT82_REGS_H
/* Section 4.1 of Data Sheet */
enum idt82v2081_reg {
IDT_REG_ID, /* control */
IDT_REG_RST,
IDT_REG_GCF,
IDT_REG_TERM,
IDT_REG_JACF,
IDT_REG_TCF0, /* Tx path control */
IDT_REG_TCF1,
IDT_REG_TCF2,
IDT_REG_TCF3,
IDT_REG_TCF4,
IDT_REG_RCF0, /* Rx path control */
IDT_REG_RCF1,
IDT_REG_RCF2,
IDT_REG_MAINT0, /* Net Diag Ctrl */
IDT_REG_MAINT1,
IDT_REG_MAINT2,
IDT_REG_MAINT3,
IDT_REG_MAINT4,
IDT_REG_MAINT5,
IDT_REG_MAINT6,
IDT_REG_INTM0, /* Interrupt Control */
IDT_REG_INTM1,
IDT_REG_INTES,
IDT_REG_STAT0, /* Line Status */
IDT_REG_STAT1,
IDT_REG_INTS0, /* Interrupt Status */
IDT_REG_INTS1,
IDT_REG_CNT0, /* Counter */
IDT_REG_CNT1,
};
#define IDT_GCF_T1E1_E1 (0 << 2)
#define IDT_GCF_T1E1_T1 (1 << 2)
#define IDT_GCF_T1E1_MASK (1 << 2)
#define IDT_TERM_T_SHIFT 3
#define IDT_TERM_T_MASK (7 << IDT_TERM_T_SHIFT)
#define IDT_TERM_R_SHIFT 0
#define IDT_TERM_R_MASK (7 << IDT_TERM_R_SHIFT)
#define IDT_TCF1_PULS_MASK 0xF
#define IDT_TCF2_SCAL_MASK 0x3F
#define IDT_RCF2_MG_MASK 3
#define IDT_RCF2_UPDW_SHIFT 2
#define IDT_RCF2_UPDW_MASK (3 << IDT_TERM_INT_75)
#define IDT_RCF2_SLICE_SHIFT 4
#define IDT_RCF2_SLICE_MASK (3 << IDT_RCF2_SLICE_SHIFT)
#define IDT_INTM0_EQ (1 << 7) /* equalizer out of range */
#define IDT_INTM0_IBLBA (1 << 6) /* in-band LB act detect */
#define IDT_INTM0_IBLBD (1 << 5) /* in-band LB deact detect */
#define IDT_INTM0_PRBS (1 << 4) /* prbs sync signal detect */
#define IDT_INTM0_TCLK (1 << 3) /* tclk loss */
#define IDT_INTM0_DF (1 << 2) /* driver failure */
#define IDT_INTM0_AIS (1 << 1) /* Alarm Indication Signal */
#define IDT_INTM0_LOS (1 << 0) /* Loss Of Signal */
#define IDT_INTM1_DAC_OV (1 << 7) /* DAC arithmetic overflow */
#define IDT_INTM1_JA_OV (1 << 6) /* JA overflow */
#define IDT_INTM1_JA_UD (1 << 5) /* JA underflow */
#define IDT_INTM1_ERR (1 << 4) /* PRBS/QRBS logic error detect */
#define IDT_INTM1_EXZ (1 << 3) /* Receive excess zeros */
#define IDT_INTM1_CV (1 << 2) /* Receive error */
#define IDT_INTM1_TIMER (1 << 1) /* One second timer expiration */
#define IDT_INTM1_CNT (1 << 0) /* Counter overflow */
/* STAT0 == INTES == INTS0 == INTM0 */
/* INTS1 == INTM1 */
#define IDT_STAT1_RLP (1 << 5)
#define IDT_STAT1_ATT_MASK 0x1F
#endif /* _IDT82_REGS_H */

View File

@ -0,0 +1,70 @@
/* (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.
*/
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <libusb.h>
#include "idt82v2081.h"
#include "idt82v2081_usb.h"
/* Adaption layer between idt82 driver and libusb */
struct idt82_libusb_infos {
struct libusb_device_handle *devh;
uint8_t ep;
};
/* backend function for core idt82 driver */
int idt82_reg_read(struct idt82 *idt, uint8_t reg)
{
struct idt82_libusb_infos *pi = idt->priv;
int rv;
uint8_t val;
rv = libusb_control_transfer(pi->devh, 0xc2, 0x02, reg, pi->ep, &val, 1, 1000);
if (rv != 1)
return -EPIPE;
return val;
}
/* backend function for core idt82 driver */
int idt82_reg_write(struct idt82 *idt, uint8_t reg, uint8_t val)
{
struct idt82_libusb_infos *pi = idt->priv;
int rv;
rv = libusb_control_transfer(pi->devh, 0x42, 0x01, reg, pi->ep, &val, 1, 1000);
if (rv != 1)
return -EPIPE;
return 0;
}
int idt82_usb_init(struct idt82 *idt, struct libusb_device_handle *devh, uint8_t ep)
{
struct idt82_libusb_infos *pi;
idt->priv = pi = malloc(sizeof(struct idt82_libusb_infos));
pi->devh = devh;
pi->ep = ep;
return 0;
}

View File

@ -0,0 +1,8 @@
#pragma once
#include <stdint.h>
struct idt82;
struct libusb_device_handle;
int idt82_usb_init(struct idt82 *idt, struct libusb_device_handle *devh, uint8_t ep);

451
software/e1-tracer/main.c Normal file
View File

@ -0,0 +1,451 @@
/* (C) 2019 by Sylvain Munaut <tnt@246tNt.com>
* (C) 2019-2020 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.
*/
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <errno.h>
#include <sys/time.h>
#include <libusb.h>
#include "idt82v2081.h"
#include "idt82v2081_usb.h"
#include "idt82v2081_regs.h"
static int g_do_exit = 0;
#define USB_VID 0x1d50
#define USB_PID 0x6151
#define EP_DATA_IN0 0x81
#define EP_DATA_IN1 0x82
struct e1_streamer;
struct flow;
typedef int (*xfer_cb_t)(struct e1_streamer *e1s, struct flow *flow, uint8_t *buf, int size);
struct flow_entry {
uint8_t *buf;
struct libusb_transfer *xfr;
};
struct flow {
struct e1_streamer *parent;
xfer_cb_t cb;
int ep;
int count;
int size;
int ppx;
struct flow_entry *entries;
};
struct e1_streamer {
struct libusb_device_handle *devh;
struct flow data_in[2];
struct idt82 liu[2];
FILE *fh;
};
struct e1_chunk_hdr {
uint32_t magic;
struct {
uint64_t sec;
uint64_t usec;
} time;
int16_t len;
uint8_t ep;
} __attribute__((packed));
static int
cb_xfr_data_in(struct e1_streamer *e1s, struct flow *flow, uint8_t *buf, int size)
{
struct e1_chunk_hdr hdr;
struct timeval tv;
int rc;
hdr.magic = 0xe115600d; /* E1 is good */
gettimeofday(&tv, NULL);
hdr.time.sec = tv.tv_sec;
hdr.time.usec = tv.tv_usec;
hdr.ep = flow->ep;
hdr.len = size;
if (size < 0) {
printf("EP %02x - Err %d: %s\n", flow->ep, size, libusb_strerror(size));
return 0;
}
if (!e1s->fh)
return 0;
rc = fwrite(&hdr, sizeof(struct e1_chunk_hdr), 1, e1s->fh);
if (rc != 1) {
fprintf(stderr, "[!] Short write: %d != %zd", rc, sizeof(struct e1_chunk_hdr));
if (rc == -1)
fprintf(stderr, ", %s\n", strerror(errno));
else
fprintf(stderr, "\n");
g_do_exit = 1;
}
if (size > 0) {
rc = fwrite(buf, size, 1, e1s->fh);
if (rc != 1) {
fprintf(stderr, "[!] Short write: %d != %zd", rc, sizeof(struct e1_chunk_hdr));
if (rc == -1)
fprintf(stderr, ", %s\n", strerror(errno));
else
fprintf(stderr, "\n");
g_do_exit = 1;
}
}
return 0;
}
static void LIBUSB_CALL cb_xfr(struct libusb_transfer *xfr)
{
struct flow *flow = (struct flow *) xfr->user_data;
struct e1_streamer *e1s = flow->parent;
int j, rv, len;
#if 0
fprintf(stderr, "transfer status (%02x) %d [%d %d] [%d %d]\n", flow->ep, xfr->status,
xfr->iso_packet_desc[0].status,
xfr->iso_packet_desc[0].actual_length,
xfr->iso_packet_desc[1].status,
xfr->iso_packet_desc[1].actual_length
);
#endif
if (xfr->status != LIBUSB_TRANSFER_COMPLETED) {
fprintf(stderr, "[!] XFR status != completed (%d)\n", xfr->status);
g_do_exit = 1;
}
len = 0;
if (flow->ep & 0x80) {
for (j=0; j<flow->ppx; j++) {
flow->cb(e1s, flow,
libusb_get_iso_packet_buffer_simple(xfr, j),
(xfr->iso_packet_desc[j].status == LIBUSB_TRANSFER_COMPLETED) ?
xfr->iso_packet_desc[j].actual_length : -1
);
if (!(xfr->iso_packet_desc[j].status == LIBUSB_TRANSFER_COMPLETED)) {
fprintf(stderr, "[!] ISO packet status != completed (%d)\n",
xfr->iso_packet_desc[j].status);
}
len += (xfr->iso_packet_desc[j].length = flow->size);
}
} else {
for (j=0; j<flow->ppx; j++)
len += (xfr->iso_packet_desc[j].length = flow->cb(e1s, flow, &xfr->buffer[len], flow->size));
}
libusb_fill_iso_transfer(xfr, e1s->devh, flow->ep,
xfr->buffer, len, flow->ppx,
cb_xfr, flow, 0
);
rv = libusb_submit_transfer(xfr);
if (rv) {
fprintf(stderr, "[!] Error re-submitting buffer (%d): %s\n", rv, libusb_strerror(rv));
g_do_exit = 1;
}
}
static void
_e1s_flow_fini(struct flow *flow)
{
int i;
for (i=0; i<flow->count; i++)
free(flow->entries[i].buf);
free(flow->entries);
}
static void
_e1s_flow_init(struct e1_streamer *e1s, struct flow *flow, xfer_cb_t cb, int ep, int count, int size, int ppx)
{
int i;
flow->parent = e1s;
flow->cb = cb;
flow->ep = ep;
flow->count = count;
flow->size = size;
flow->ppx = ppx;
flow->entries = calloc(count, sizeof(struct flow_entry));
for (i=0; i<count; i++)
flow->entries[i].buf = malloc(size * ppx);
}
static int
_e1s_flow_start(struct e1_streamer *e1s, struct flow *flow)
{
struct libusb_transfer *xfr;
int i, j, rv, len;
for (i=0; i<flow->count; i++)
{
xfr = libusb_alloc_transfer(flow->ppx);
if (!xfr)
return -ENOMEM;
len = 0;
if (flow->ep & 0x80) {
for (j=0; j<flow->ppx; j++)
len += (xfr->iso_packet_desc[j].length = flow->size);
} else {
for (j=0; j<flow->ppx; j++)
len += (xfr->iso_packet_desc[j].length = flow->cb(e1s, flow, &flow->entries[i].buf[len], flow->size));
}
libusb_fill_iso_transfer(xfr, e1s->devh, flow->ep,
flow->entries[i].buf, len, flow->ppx,
cb_xfr, flow, 0
);
rv = libusb_submit_transfer(xfr);
if (rv) {
return rv;
}
flow->entries[i].xfr = xfr;
}
return 0;
}
static void
e1s_release(struct e1_streamer *e1s)
{
if (!e1s)
return;
_e1s_flow_fini(&e1s->data_in[0]);
_e1s_flow_fini(&e1s->data_in[1]);
if (e1s->devh) {
libusb_release_interface(e1s->devh, 0);
libusb_close(e1s->devh);
}
free(e1s);
}
static struct e1_streamer *
e1s_new(bool monitor, const char *out_file, bool append, int nx, int ppx)
{
struct e1_streamer *e1s = NULL;
int rv;
e1s = calloc(1, sizeof(struct e1_streamer));
if (!e1s)
return NULL;
e1s->devh = libusb_open_device_with_vid_pid(NULL, USB_VID, USB_PID);
if (!e1s->devh) {
fprintf(stderr, "Error finding USB device\n");
goto err;
}
rv = libusb_claim_interface(e1s->devh, 0);
if (rv < 0) {
fprintf(stderr, "Error claiming interface: %s\n", libusb_error_name(rv));
goto err;
}
rv = libusb_set_interface_alt_setting(e1s->devh, 0, 1);
if (rv < 0) {
fprintf(stderr, "Error enabling interface: %s\n", libusb_error_name(rv));
goto err;
}
_e1s_flow_init(e1s, &e1s->data_in[0], cb_xfr_data_in, EP_DATA_IN0, nx, 388, ppx);
_e1s_flow_init(e1s, &e1s->data_in[1], cb_xfr_data_in, EP_DATA_IN1, nx, 388, ppx);
idt82_usb_init(&e1s->liu[0], e1s->devh, EP_DATA_IN0);
idt82_usb_init(&e1s->liu[1], e1s->devh, EP_DATA_IN1);
idt82_init(&e1s->liu[0], monitor);
idt82_init(&e1s->liu[1], monitor);
if (out_file) {
e1s->fh = fopen(out_file, append ? "ab" : "wb");
if (!e1s->fh)
fprintf(stderr, "[1] Failed to open recording file\n");
}
return e1s;
err:
e1s_release(e1s);
return NULL;
}
struct options {
/* Transfer config */
int nx;
int ppx;
/* Output */
const char *out_filename;
bool out_append;
/* PHY */
bool monitor;
/* OS */
bool realtime;
};
static void
opts_defaults(struct options *opts)
{
memset(opts, 0x00, sizeof(struct options));
opts->nx = 2;
opts->ppx = 4;
}
static void
opts_help(void)
{
fprintf(stderr, " -a Output : append mode\n");
fprintf(stderr, " -o FILE Output : filename\n");
fprintf(stderr, " -n NX Xfer : Number of queued transfers (default: 2)\n");
fprintf(stderr, " -p PPX Xfer : Number of packets per transfer (default: 4)\n");
fprintf(stderr, " -m PHY : Monitor mode (i.e. high gain)\n");
fprintf(stderr, " -r OS : Set real-time priority on process\n");
fprintf(stderr, " -h help\n");
}
static int
opts_parse(struct options *opts, int argc, char *argv[])
{
const char *opts_short = "ao:n:p:mrh";
int opt;
while ((opt = getopt(argc, argv, opts_short)) != -1)
{
switch(opt) {
case 'a':
opts->out_append = true;
break;
case 'o':
opts->out_filename = optarg;
break;
case 'n':
opts->nx = atoi(optarg);
if (opts->nx <= 0) {
fprintf(stderr, "[!] Invalid nx value ignored\n");
opts->nx = 2;
}
break;
case 'p':
opts->ppx = atoi(optarg);
if (opts->ppx <= 0) {
fprintf(stderr, "[!] Invalid ppx value ignored\n");
opts->ppx = 4;
}
break;
case 'm':
opts->monitor = true;
break;
case 'r':
opts->realtime = true;
break;
case 'h':
default:
opts_help();
exit(1);
}
}
return 0;
}
int main(int argc, char *argv[])
{
struct e1_streamer *e1s;
struct sched_param sp;
struct options opts;
int rv;
opts_defaults(&opts);
opts_parse(&opts, argc, argv);
if (opts.realtime) {
memset(&sp, 0x00, sizeof(sp));
sp.sched_priority = 50;
rv = sched_setscheduler(0, SCHED_RR, &sp);
printf("%d %d\n", rv, errno);
perror("sched_setscheduler");
}
rv = libusb_init(NULL);
if (rv < 0) {
fprintf(stderr, "Error initializing libusb: %s\n", libusb_error_name(rv));
return rv;
}
e1s = e1s_new(opts.monitor, opts.out_filename, opts.out_append, opts.nx, opts.ppx);
if (!e1s)
goto out;
_e1s_flow_start(e1s, &e1s->data_in[0]);
_e1s_flow_start(e1s, &e1s->data_in[1]);
while (!g_do_exit) {
rv = libusb_handle_events(NULL);
if (rv != LIBUSB_SUCCESS)
break;
}
out:
e1s_release(e1s);
libusb_exit(NULL);
return 0;
}

View File

@ -0,0 +1,710 @@
/* Osmocom Software Defined E1
* Implements ITU-T Rec. G.704 Section 2.3
*
* (C) 2018 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.
*/
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/fsm.h>
#include "crc4itu.h"
#include "osmo_e1f.h"
#define S(x) (1 << (x))
/* Frame Alignment Signal (BIT1 may be overwritten with CRC-4) */
#define G704_E1_FAS 0x1B
static inline bool is_correct_fas(uint8_t bt) {
if ((bt & 0x7F) == G704_E1_FAS)
return true;
else
return false;
}
/* are we in SMF II (true) or I (false) */
static inline bool is_smf_II(const struct osmo_e1f_tx_state *tx) {
if (tx->frame_nr >= 8)
return true;
return false;
}
static struct osmo_fsm e1_align_fsm;
static void align_fsm_reset(struct osmo_e1f_instance *e1i);
static void notify_user(struct osmo_e1f_instance *e1i, enum osmo_e1f_notify_event evt,
bool present, void *priv)
{
if (!e1i->notify_cb)
return;
e1i->notify_cb(e1i, evt, present, priv);
}
/*! Initialize a (caller-allocated) Osmocom E1 Instance
* \param[inout] e1i E1 Instance to be initialized
* \returns 0 on success, negative on error */
int osmo_e1f_instance_init(struct osmo_e1f_instance *e1i, const char *name, e1_notify_cb cb,
bool crc4_enabled, void *priv)
{
int i;
e1i->crc4_enabled = crc4_enabled;
e1i->notify_cb = cb;
e1i->tx.sa4_sa8 = 0x00;
e1i->priv = priv;
for (i = 1; i < ARRAY_SIZE(e1i->ts); i++) {
struct osmo_e1f_instance_ts *e1t = &e1i->ts[i];
e1t->ts_nr = i;
e1t->inst = e1i;
INIT_LLIST_HEAD(&e1t->tx.queue);
e1t->rx.granularity = 256;
}
e1i->rx.fi = osmo_fsm_inst_alloc(&e1_align_fsm, NULL, e1i, LOGL_DEBUG, name);
if (!e1i->rx.fi)
return -1;
osmo_e1f_instance_reset(e1i);
return 0;
}
/*! stop E1 timeslot; release any pending rx/tx buffers
* \param[in] e1t Timeslot which we are to stop, disable and release buffers */
void osmo_e1f_ts_reset(struct osmo_e1f_instance_ts *e1t)
{
e1t->tx.underruns = 0;
msgb_queue_free(&e1t->tx.queue);
e1t->rx.enabled = false;
msgb_free(e1t->rx.msg);
e1t->rx.msg = NULL;
osmo_isdnhdlc_rcv_init(&e1t->rx.hdlc, OSMO_HDLC_F_BITREVERSE);
//osmo_isdnhdlc_rcv_init(&e1t->rx.hdlc, 0);
osmo_isdnhdlc_out_init(&e1t->tx.hdlc, 0);
}
/*! stop E1 instance; stops all timeslots and releases any pending rx/tx buffers
* \param[in] e1t E1 instance which we are to stop */
void osmo_e1f_instance_reset(struct osmo_e1f_instance *e1i)
{
int i;
align_fsm_reset(e1i);
e1i->tx.remote_alarm = false;
e1i->tx.crc4_error = false;
e1i->tx.frame_nr = 0;
e1i->tx.crc4_last_smf = 0;
e1i->tx.crc4 = crc4itu_init();
e1i->rx.frame_nr = 0;
memset(&e1i->rx.ts0_history, 0, sizeof(e1i->rx.ts0_history));
e1i->rx.ts0_hist_len = 0;
e1i->rx.remote_alarm = false;
e1i->rx.remote_crc4_error = false;
e1i->rx.num_ts0_in_mframe_search = 0;
for (i = 1; i < ARRAY_SIZE(e1i->ts); i++) {
struct osmo_e1f_instance_ts *e1t = &e1i->ts[i];
osmo_e1f_ts_reset(e1t);
}
}
/*! obtain pointer to TS given by instance + timeslot number
* \param[in] e1i E1 intance on which we work
* \param[in] ts_nr E1 timeslot number (1..31)
* \returns pointer to timeslot; NULL on error */
struct osmo_e1f_instance_ts *osmo_e1f_instance_ts(struct osmo_e1f_instance *e1i, uint8_t ts_nr)
{
if (ts_nr == 0 || ts_nr >= ARRAY_SIZE(e1i->ts))
return NULL;
return &e1i->ts[ts_nr];
}
/*! configure an E1 timeslot
* \param[in] e1t Timeslot which we are to configure
* \param[in] granularity granularity (buffer size) to use on Rx
* \param[in] enable enable (true) or disalble (false) receiving on this TS
* \param[in] mode the mode for this timeslot (raw or hdlc)
* \return 0 on success; negative on error */
int osmo_e1f_ts_config(struct osmo_e1f_instance_ts *e1t, e1_data_cb cb, unsigned int granularity,
bool enable, enum osmo_e1f_ts_mode mode)
{
e1t->rx.data_cb = cb;
e1t->rx.enabled = enable;
e1t->rx.granularity = granularity;
e1t->mode = mode;
return 0;
}
const struct value_string osmo_e1f_notifv_evt_names[] = {
{ E1_NTFY_EVT_ALIGN_FRAME, "Aligned to Frame" },
{ E1_NTFY_EVT_ALIGN_CRC_MFRAME, "Aligned to CRC4-Multiframe" },
{ E1_NTFY_EVT_CRC_ERROR, "CRC Error detected (local)" },
{ E1_NTFY_EVT_REMOTE_CRC_ERROR, "CRC Error reported (remote)" },
{ E1_NTFY_EVT_REMOTE_ALARM, "Remote Alarm condition repoorted" },
{ 0, NULL }
};
/***********************************************************************
* Transmit Side
***********************************************************************/
/*! Enqueue a message buffer of to-be-transmitted data for a timeslot
* \param[in] e1i E1 instance for which to enqueue
* \param[in] ts_nr Timeslot number on which data is to be transmitted
* \param[in] msg Message buffer storing the to-be-transmitted data
* \returns 0 on success; negative in case of error.
*
* Ownership of \a msg is transferred from caller into this function, but only
* in case of successful execution (return 0)!
*/
void osmo_e1f_ts_enqueue(struct osmo_e1f_instance_ts *e1t, struct msgb *msg)
{
msgb_enqueue(&e1t->tx.queue, msg);
}
/* obtain a CRC4 bit for the current frame number */
static uint8_t e1_pull_crc4_bit(struct osmo_e1f_instance *e1i)
{
/* If CRC-4 is disabled, all CRC bits shall be '1' */
if (e1i->crc4_enabled == 0)
return 0x01;
/* CRC is transmitted MSB first */
switch (e1i->tx.frame_nr % 8) {
case 0:
return (e1i->tx.crc4_last_smf >> 3) & 1;
case 2:
return (e1i->tx.crc4_last_smf >> 2) & 1;
case 4:
return (e1i->tx.crc4_last_smf >> 1) & 1;
case 6:
return (e1i->tx.crc4_last_smf >> 0) & 1;
default:
OSMO_ASSERT(0);
}
}
/* pull a single to-be-transmitted byte for TS0 */
static uint8_t e1_pull_ts0(struct osmo_e1f_instance *e1i)
{
uint8_t ret;
/* according to Table 5B/G.704 - CRC-4 multiframe structure */
if ((e1i->tx.frame_nr % 2) == 0) {
/* FAS */
ret = G704_E1_FAS | (e1_pull_crc4_bit(e1i) << 7);
} else {
switch (e1i->tx.frame_nr) {
case 1:
case 3:
case 7:
ret = 0x40;
break;
case 5:
case 9:
case 11:
ret = 0xC0;
break;
case 13:
case 15:
ret = 0x40;
if (e1i->tx.crc4_error)
ret |= 0x80;
break;
}
ret |= e1i->tx.sa4_sa8;
if (e1i->tx.remote_alarm)
ret |= 0x20;
}
/* re-set CRC4 at start of sub-multiframe */
if (e1i->tx.frame_nr == 0 || e1i->tx.frame_nr == 8) {
e1i->tx.crc4_last_smf = e1i->tx.crc4;
e1i->tx.crc4 = 0;
}
/* increment frame number modulo 16 */
e1i->tx.frame_nr = (e1i->tx.frame_nr + 1) % 16;
return ret;
}
/* pull a single to-be-transmitted byte for TS1..31 */
static uint8_t e1_pull_tsN(struct osmo_e1f_instance_ts *e1t)
{
struct msgb *msg = llist_first_entry_or_null(&e1t->tx.queue, struct msgb, list);
uint8_t *cur;
retry:
/* if there's no message to transmit */
if (!msg) {
e1t->tx.underruns++;
return 0xFF;
}
if (msgb_length(msg) <= 0) {
llist_del(&msg->list);
msgb_free(msg);
msg = llist_first_entry_or_null(&e1t->tx.queue, struct msgb, list);
goto retry;
}
cur = msgb_pull(msg, 1);
return *cur;
}
/* update the current in-progress CRC4 value with data from \a out_frame */
static void e1_tx_update_crc4(struct osmo_e1f_instance *e1i, const uint8_t *out_frame)
{
uint8_t ts0;
ts0 = out_frame[0];
/* mask off the C bits */
if (is_correct_fas(ts0))
ts0 &= 0x7F;
e1i->tx.crc4 = crc4itu_update(e1i->tx.crc4, &ts0, 1);
/* add the remaining bytes/bits */
e1i->tx.crc4 = crc4itu_update(e1i->tx.crc4, out_frame+1, ARRAY_SIZE(e1i->ts)-1);
}
/*! Pull one to-be-transmitted E1 frame (256bits) from the E1 instance
* \param e1i E1 instance for which the frame shall be generated
* \param[out] out_frame callee-allocated buffer to which function stores 32 bytes
* \returns 0 on success, negative on error */
int osmo_e1f_pull_tx_frame(struct osmo_e1f_instance *e1i, uint8_t *out_frame)
{
int i;
/* generate TS0 */
out_frame[0] = e1_pull_ts0(e1i);
/* generate TS1..31 */
for (i = 1; i < ARRAY_SIZE(e1i->ts); i++) {
struct osmo_e1f_instance_ts *e1t = &e1i->ts[i];
/* get next to-be-transmitted byte from the TS */
out_frame[i] = e1_pull_tsN(e1t);
}
/* update our CRC4 computation */
e1_tx_update_crc4(e1i, out_frame);
return 0;
}
/***********************************************************************
* Receiver Side
***********************************************************************/
/* According to Figure 2 / ITU-T G.706 */
enum e1_align_state {
/* Frame Alignment Search */
E1_AS_SEARCH_FRAME,
/* CRC multiframe alignment search */
E1_AS_SEARCH_CRC_MFRAME,
/* monitoring for incorrect frame alignment and error performance using CRC */
E1_AS_ALIGNED_CRC_MFRAME,
/* no CRC: just frame alignment loss check */
E1_AS_ALIGNED_BASIC,
};
enum e1_align_event {
/* received a TS0 octet */
E1_AE_RX_TS0,
E1_AE_RESET
};
static const struct value_string e1_align_evt_names[] = {
{ E1_AE_RX_TS0, "E1_AE_RX_TS0" },
{ E1_AE_RESET, "E1_AE_RESET" },
{ 0, NULL }
};
/* get a TS0 byte from the history. delta 0 == current, delte 1 == previous, ... */
static uint8_t get_ts0_hist(struct osmo_e1f_instance *e1i, uint8_t delta)
{
return e1i->rx.ts0_history[((e1i->rx.frame_nr + 16)-delta) % 16];
}
/* ITU-T G.706 Section 4.1.1 */
static bool frame_alignment_lost(struct osmo_e1f_instance *e1i)
{
if (e1i->rx.frame_nr % 2)
return false;
/* Frame alignment will be assumed to have been lost when three consecutive incorrect
* frame alignment signals have been received. */
if (!is_correct_fas(get_ts0_hist(e1i, 0)) &&
!is_correct_fas(get_ts0_hist(e1i, 2)) &&
!is_correct_fas(get_ts0_hist(e1i, 4)))
return true;
else
return false;
}
/* ITU-T G.706 Section 4.1.2 */
static bool frame_alignment_recovered(struct osmo_e1f_instance *e1i)
{
/* two consecutive FAS with one non-FAS interspersed */
if (is_correct_fas(get_ts0_hist(e1i, 0)) &&
!is_correct_fas(get_ts0_hist(e1i, 1)) &&
is_correct_fas(get_ts0_hist(e1i, 2)))
return true;
else
return false;
}
/* ITU-T G.706 Section 4.2 */
static bool crc_mframe_alignment_achieved(struct osmo_e1f_instance *e1i)
{
/* if current TS0 byte is FAS, we cannot detect alignment */
if (is_correct_fas(get_ts0_hist(e1i, 0)))
return false;
if ((get_ts0_hist(e1i, 0) >> 7) == 1 &&
(get_ts0_hist(e1i, 2) >> 7) == 1 &&
(get_ts0_hist(e1i, 4) >> 7) == 0 &&
(get_ts0_hist(e1i, 6) >> 7) == 1 &&
(get_ts0_hist(e1i, 8) >> 7) == 0 &&
(get_ts0_hist(e1i, 10) >> 7) == 0)
return true;
else
return false;
}
/* Get the CRC4 that was received from our Rx TS0 history */
static uint8_t crc4_from_ts0_hist(struct osmo_e1f_instance *e1i, bool smf2)
{
uint8_t crc = 0;
uint8_t offset = 0;
if (smf2)
offset = 8;
crc |= (e1i->rx.ts0_history[0+offset] >> 7) << 3;
crc |= (e1i->rx.ts0_history[2+offset] >> 7) << 2;
crc |= (e1i->rx.ts0_history[4+offset] >> 7) << 1;
crc |= (e1i->rx.ts0_history[6+offset] >> 7) << 0;
return crc;
}
/* update the current in-progress CRC4 value with data from \a rx_frame */
static void e1_rx_update_crc4(struct osmo_e1f_instance *e1i, const uint8_t *rx_frame)
{
uint8_t ts0;
ts0 = rx_frame[0];
/* mask off the C bits */
if (is_correct_fas(ts0))
ts0 &= 0x7F;
e1i->rx.crc4 = crc4itu_update(e1i->rx.crc4, &ts0, 1);
/* add the remaining bytes/bits */
e1i->rx.crc4 = crc4itu_update(e1i->rx.crc4, rx_frame+1, ARRAY_SIZE(e1i->ts)-1);
}
/* FSM State handler */
static void e1_align_search_frame(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct osmo_e1f_instance *e1i = (struct osmo_e1f_instance *) fi->priv;
if (frame_alignment_recovered(e1i)) {
/* if we detected the 2nd FAS, we must be in FN 2 (or at least FN%2=0 */
e1i->rx.frame_nr = 2;
notify_user(e1i, E1_NTFY_EVT_ALIGN_FRAME, true, NULL);
osmo_fsm_inst_state_chg(fi, E1_AS_SEARCH_CRC_MFRAME, 0, 0);
}
}
/* FSM State handler */
static void e1_align_search_crc_mframe(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct osmo_e1f_instance *e1i = (struct osmo_e1f_instance *) fi->priv;
if (crc_mframe_alignment_achieved(e1i)) {
/* if we detected the 6-bit CRC multiframe signal, we must be in FN 11 */
e1i->rx.frame_nr = 11;
/* FIXME: "at least two valid CRC multiframe alignment signals can be located within
* 8 ms, the time separating two CRC multiframe alignment signals being 2 ms or a
* multiple of 2 ms" */
notify_user(e1i, E1_NTFY_EVT_ALIGN_CRC_MFRAME, true, NULL);
osmo_fsm_inst_state_chg(fi, E1_AS_ALIGNED_CRC_MFRAME, 0, 0);
} else {
/* if no mframe alignment is established within 8ms (64 frames), fall back */
if (e1i->rx.num_ts0_in_mframe_search >= 64) {
e1i->rx.num_ts0_in_mframe_search = 0;
osmo_fsm_inst_state_chg(fi, E1_AS_SEARCH_FRAME, 0, 0);
}
e1i->rx.num_ts0_in_mframe_search++;
}
}
static void e1_aligned_common(struct osmo_e1f_instance *e1i)
{
uint8_t inb = get_ts0_hist(e1i, 0);
/* All non-FAS frames contain "A" bit in TS0 */
if (!is_correct_fas(inb & 0x7F)) {
bool old_alarm = e1i->rx.remote_alarm;
/* frame not containing the frame alignment signal */
if (inb & 0x20)
e1i->rx.remote_alarm = true;
else
e1i->rx.remote_alarm = false;
if (old_alarm != e1i->rx.remote_alarm)
notify_user(e1i, E1_NTFY_EVT_REMOTE_ALARM, e1i->rx.remote_alarm, NULL);
}
}
/* FSM State handler */
static void e1_aligned_crc_mframe(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct osmo_e1f_instance *e1i = (struct osmo_e1f_instance *) fi->priv;
if (frame_alignment_lost(e1i)) {
osmo_fsm_inst_state_chg(fi, E1_AS_SEARCH_FRAME, 0, 0);
return;
}
if (e1i->crc4_enabled) {
uint8_t crc_rx;
bool crc4_error;
/* check if we just received a complete CRC4 */
switch (e1i->rx.frame_nr) {
case 7:
case 15:
crc_rx = crc4_from_ts0_hist(e1i, e1i->rx.frame_nr == 15 ? true : false);
if (crc_rx != e1i->rx.crc4_last_smf)
crc4_error = true;
else
crc4_error = false;
if (crc4_error != e1i->tx.crc4_error) {
notify_user(e1i, E1_NTFY_EVT_CRC_ERROR, crc4_error, NULL);
e1i->tx.crc4_error = crc4_error;
}
/* rotate computed CRC4 one further */
e1i->rx.crc4_last_smf = e1i->rx.crc4;
e1i->rx.crc4 = crc4itu_init();
break;
default:
break;
}
/* check if the remote side reports any CRC errors */
switch (e1i->rx.frame_nr) {
case 13:
case 15:
crc4_error = false;
if ((get_ts0_hist(e1i, 0) >> 7) == 0)
crc4_error = true;
if (crc4_error != e1i->rx.remote_crc4_error) {
notify_user(e1i, E1_NTFY_EVT_REMOTE_CRC_ERROR, crc4_error, NULL);
e1i->rx.remote_crc4_error = crc4_error;
}
break;
}
}
e1_aligned_common(e1i);
}
/* FSM State handler */
static void e1_aligned_basic(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct osmo_e1f_instance *e1i = (struct osmo_e1f_instance *) fi->priv;
if (frame_alignment_lost(e1i)) {
osmo_fsm_inst_state_chg(fi, E1_AS_SEARCH_FRAME, 0, 0);
return;
}
e1_aligned_common(e1i);
}
static const struct osmo_fsm_state e1_align_states[] = {
[E1_AS_SEARCH_FRAME] = {
.name = "SEARCH_FRAME",
.in_event_mask = S(E1_AE_RX_TS0),
.out_state_mask = S(E1_AS_SEARCH_FRAME) |
S(E1_AS_SEARCH_CRC_MFRAME) |
S(E1_AS_ALIGNED_BASIC),
.action = e1_align_search_frame,
},
[E1_AS_SEARCH_CRC_MFRAME] = {
.name = "SEARCH_CRC_MFRAME",
.in_event_mask = S(E1_AE_RX_TS0),
.out_state_mask = S(E1_AS_SEARCH_FRAME) |
S(E1_AS_SEARCH_CRC_MFRAME) |
S(E1_AS_ALIGNED_CRC_MFRAME),
.action = e1_align_search_crc_mframe,
},
[E1_AS_ALIGNED_CRC_MFRAME] = {
.name = "ALIGNED_CRC_MFRAME",
.in_event_mask = S(E1_AE_RX_TS0),
.out_state_mask = S(E1_AS_SEARCH_FRAME) |
S(E1_AS_SEARCH_CRC_MFRAME) |
S(E1_AS_ALIGNED_CRC_MFRAME),
.action = e1_aligned_crc_mframe,
},
[E1_AS_ALIGNED_BASIC] = {
.name = "ALIGNED_BASIC",
.in_event_mask = S(E1_AE_RX_TS0),
.out_state_mask = S(E1_AS_SEARCH_FRAME),
.action = e1_aligned_basic,
},
};
static void e1_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct osmo_e1f_instance *e1i = (struct osmo_e1f_instance *) fi->priv;
switch (event) {
case E1_AE_RESET:
e1i->rx.num_ts0_in_mframe_search = 0;
osmo_fsm_inst_state_chg(fi, E1_AS_SEARCH_FRAME, 0, 0);
break;
}
}
static struct osmo_fsm e1_align_fsm = {
.name = "e1-align",
.states = e1_align_states,
.num_states = ARRAY_SIZE(e1_align_states),
.allstate_event_mask = S(E1_AE_RESET),
.allstate_action = e1_allstate,
.log_subsys = DLGLOBAL,
.event_names = e1_align_evt_names,
};
static void align_fsm_reset(struct osmo_e1f_instance *e1i)
{
osmo_fsm_inst_dispatch(e1i->rx.fi, E1_AE_RESET, NULL);
}
static void e1_rx_hist_add(struct osmo_e1f_instance *e1i, uint8_t inb)
{
e1i->rx.ts0_history[e1i->rx.frame_nr] = inb;
if (e1i->rx.ts0_hist_len < 16)
e1i->rx.ts0_hist_len++;
}
static void e1_rx_ts0(struct osmo_e1f_instance *e1i, uint8_t inb)
{
/* append just-received byte to the TS0 receive history buffer */
e1_rx_hist_add(e1i, inb);
/* notify the FSM that a new TS0 byte was received */
osmo_fsm_inst_dispatch(e1i->rx.fi, E1_AE_RX_TS0, NULL);
e1i->rx.frame_nr = (e1i->rx.frame_nr + 1) % 16;
}
static void e1_rx_tsN(struct osmo_e1f_instance_ts *e1t, uint8_t inb)
{
struct msgb *msg;
int count, rc;
if (!e1t->rx.enabled)
return;
if (!e1t->rx.msg)
e1t->rx.msg = msgb_alloc(e1t->rx.granularity, "E1 Rx");
msg = e1t->rx.msg;
OSMO_ASSERT(msg);
switch (e1t->mode) {
case OSMO_E1F_TS_RAW:
/* append byte at end of msgb */
msgb_put_u8(msg, inb);
/* flush msgb, if full */
if (msgb_tailroom(msg) <= 0) {
goto flush;
}
break;
case OSMO_E1F_TS_HDLC_CRC:
rc = osmo_isdnhdlc_decode(&e1t->rx.hdlc, &inb, 1, &count,
msgb_data(msg), msgb_tailroom(msg));
switch (rc) {
case -OSMO_HDLC_FRAMING_ERROR:
fprintf(stdout, "Framing Error\n");
break;
case -OSMO_HDLC_CRC_ERROR:
fprintf(stdout, "CRC Error\n");
break;
case -OSMO_HDLC_LENGTH_ERROR:
fprintf(stdout, "Length Error\n");
break;
case 0:
/* no output yet */
break;
default:
msgb_put(msg, rc);
goto flush;
}
break;
}
return;
flush:
if (!e1t->rx.data_cb)
msgb_free(msg);
else
e1t->rx.data_cb(e1t, msg);
e1t->rx.msg = NULL;
}
/*! Receive a single E1 frame of 32x8 (=256) bits
* \param e1i E1 instance for which the frame was received
* \param[in] in_frame caller-provided buffer of 32 octets
*
* The idea is that whoever calls us will already have done the bit-alignment,
* i.e. the first bit of TS0 of the frame will be octet-aligned and hence the
* entire 256bit buffer is provided as octet-aligned 32bytes in \a in_frame.
*/
int osmo_e1f_rx_frame(struct osmo_e1f_instance *e1i, const uint8_t *in_frame)
{
int i;
e1_rx_update_crc4(e1i, in_frame);
e1_rx_ts0(e1i, in_frame[0]);
for (i = 1; i < ARRAY_SIZE(e1i->ts); i++) {
struct osmo_e1f_instance_ts *e1t = &e1i->ts[i];
e1_rx_tsN(e1t, in_frame[i]);
}
return 0;
}
int osmo_e1f_init(void)
{
return osmo_fsm_register(&e1_align_fsm);
}

View File

@ -0,0 +1,141 @@
#pragma once
/* Osmocom Software Defined E1
* Implements ITU-T Rec. G.704 Section 2.3
*
* (C) 2018 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.
*/
#include <stdint.h>
#include <stdbool.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/isdnhdlc.h>
struct osmo_e1f_tx_state {
bool remote_alarm;
bool crc4_error;
/* lower 5 bits: Sa4..Sa8 */
uint8_t sa4_sa8;
/* frame number 0..15 */
uint8_t frame_nr;
uint8_t crc4_last_smf;
uint8_t crc4;
};
struct osmo_e1f_rx_state {
uint8_t frame_nr;
/* history of rceived TS0 octets */
uint8_t ts0_history[16];
uint8_t ts0_hist_len;
/* was a remote alarm received? */
bool remote_alarm;
bool remote_crc4_error;
/* number of TS0 bytes received since entering CRC mframe search */
uint8_t num_ts0_in_mframe_search;
struct osmo_fsm_inst *fi;
/* computed CRC4 */
uint8_t crc4_last_smf;
uint8_t crc4;
};
enum osmo_e1f_notify_event {
E1_NTFY_EVT_ALIGN_FRAME,
E1_NTFY_EVT_ALIGN_CRC_MFRAME,
E1_NTFY_EVT_CRC_ERROR,
E1_NTFY_EVT_REMOTE_CRC_ERROR,
E1_NTFY_EVT_REMOTE_ALARM,
};
enum osmo_e1f_ts_mode {
OSMO_E1F_TS_RAW,
OSMO_E1F_TS_HDLC_CRC,
};
struct osmo_e1f_instance_ts;
struct osmo_e1f_instance;
typedef void (*e1_data_cb)(struct osmo_e1f_instance_ts *ts, struct msgb *msg);
typedef void (*e1_notify_cb)(struct osmo_e1f_instance *e1i, enum osmo_e1f_notify_event evt,
bool present, void *data);
struct osmo_e1f_instance_ts {
/* timeslot number */
uint8_t ts_nr;
/* mode in which we operate (RAW/HDLC) */
enum osmo_e1f_ts_mode mode;
/* back-pointer to e1 instance */
struct osmo_e1f_instance *inst;
struct {
/* optional HDLC encoder state */
struct osmo_isdnhdlc_vars hdlc;
/* queue of pending to-be-transmitted messages */
struct llist_head queue;
unsigned long underruns;
} tx;
struct {
/* optional HDLC decoder state */
struct osmo_isdnhdlc_vars hdlc;
bool enabled;
/* how many bytes to buffer before calling call-back */
unsigned int granularity;
/* current receive buffer */
struct msgb *msg;
e1_data_cb data_cb;
/* private data, relevant to user */
void *priv;
} rx;
};
struct osmo_e1f_instance {
/* list; currently not used yet */
struct llist_head list;
/* is CRC4 generation + parsing enabled? */
bool crc4_enabled;
/* notification call-back function */
e1_notify_cb notify_cb;
/* Rx + Tx related state */
struct osmo_e1f_tx_state tx;
struct osmo_e1f_rx_state rx;
/* our 32 timeslots (only 1..32 are used) */
struct osmo_e1f_instance_ts ts[32];
/* private data, relevant to user */
void *priv;
};
extern const struct value_string osmo_e1f_notifv_evt_names[];
static inline const char *osmo_e1f_notify_event_name(enum osmo_e1f_notify_event evt) {
return get_value_string(osmo_e1f_notifv_evt_names, evt);
}
int osmo_e1f_init(void);
struct osmo_e1f_instance_ts *osmo_e1f_instance_ts(struct osmo_e1f_instance *e1i, uint8_t ts_nr);
int osmo_e1f_instance_init(struct osmo_e1f_instance *e1i, const char *name, e1_notify_cb cb,
bool crc4_enabled, void *priv);
void osmo_e1f_instance_reset(struct osmo_e1f_instance *e1i);
int osmo_e1f_ts_config(struct osmo_e1f_instance_ts *e1t, e1_data_cb cb, unsigned int granularity,
bool enable, enum osmo_e1f_ts_mode mode);
void osmo_e1f_ts_reset(struct osmo_e1f_instance_ts *e1t);
void osmo_e1f_ts_enqueue(struct osmo_e1f_instance_ts *e1t, struct msgb *msg);
int osmo_e1f_pull_tx_frame(struct osmo_e1f_instance *e1i, uint8_t *out_frame);
int osmo_e1f_rx_frame(struct osmo_e1f_instance *e1i, const uint8_t *in_frame);

173
software/e1-tracer/replay.c Normal file
View File

@ -0,0 +1,173 @@
/* (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.
*/
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <inttypes.h>
#include <time.h>
#include <assert.h>
#include <osmocom/core/utils.h>
#define E1_TS_BITRATE 64000
#define E1_31TS_BITRATE (31*E1_TS_BITRATE)
#define E1_31TS_BYTERATE (E1_31TS_BITRATE/8)
#define E1_CHUNK_HDR_MAGIC 0xe115600d /* E1 is good */
struct e1_chunk_hdr {
uint32_t magic;
struct {
uint64_t sec;
uint64_t usec;
} time;
uint16_t len; /* length of following payload */
uint8_t ep; /* USB endpoint */
} __attribute__((packed));
static struct timespec ts_start;
static uint64_t bytes_total;
static uint64_t get_msecs_total(void)
{
struct timespec ts_now;
clock_gettime(CLOCK_MONOTONIC, &ts_now);
return (ts_now.tv_sec*1000 + ts_now.tv_nsec/1000000) - (ts_start.tv_sec*1000 + ts_start.tv_nsec/1000000);
}
/* called for each USB transfer read from the file */
static void handle_frame(const struct e1_chunk_hdr *hdr, const uint8_t *data)
{
uint8_t nots0[1024];
unsigned int offs = 0;
/* filter on the endpoint (direction) specified by the user */
switch (hdr->ep) {
case 0x81:
break;
case 0x82:
return;
default:
fprintf(stderr, "Unexpected USB EP 0x%02x\n", hdr->ep);
return;
}
if (hdr->len <= 4)
return;
//printf("%u: %"PRIu64".%"PRIu64" EP=0x%02x\n", ls->idx, hdr->time.sec, hdr->time.usec, hdr->ep);
assert(((hdr->len-4)/32)*31 < ARRAY_SIZE(nots0));
/* gather the TS1..TS31 data, skipping TS0 */
for (int i = 4; i < hdr->len-4; i += 32) {
//printf("%u:\t%s\n", ls->idx, osmo_hexdump(data+i, 32));
memcpy(nots0+offs, data+i+1, 32-1);
offs += 31;
write(1, data+i, 31);
bytes_total += 31;
}
/* check number of bytes written / number of seconds expired -> sleep or not? */
uint64_t msecs_total = get_msecs_total();
if (msecs_total == 0)
return;
uint64_t bytes_per_sec = bytes_total * 1000 / msecs_total;
//fprintf(stderr, "%"PRIu64" msecs, %"PRIu64" bytes, %"PRIu64" bytes/s\n", msecs_total, bytes_total, bytes_per_sec);
if (bytes_per_sec > E1_31TS_BYTERATE) {
uint64_t msecs_expected = bytes_total * 1000 / E1_31TS_BYTERATE;
//fprintf(stderr, "expected: %"PRIu64" msecs, actual: %"PRIu64" msecs, sleepint\n", msecs_expected, msecs_total);
usleep((msecs_expected - msecs_total)*1000);
}
}
static int process_file(void *map, off_t len)
{
unsigned long offset = 0;
clock_gettime(CLOCK_MONOTONIC, &ts_start);
while (offset < len) {
struct e1_chunk_hdr *hdr = map + offset;
offset += sizeof(*hdr);
if (hdr->magic != E1_CHUNK_HDR_MAGIC) {
fprintf(stderr, "offset %lu: Wrong magic 0x%08x\n", offset, hdr->magic);
return -1;
}
/* then read payload */
handle_frame(hdr, (const uint8_t *)map + offset);
offset += hdr->len;
}
return 0;
}
static void *open_mmap_file(const char *fname, off_t *size)
{
int fd, rc;
struct stat st;
void *map;
fd = open(fname, O_RDONLY);
if (fd < 0)
return NULL;
rc = fstat(fd, &st);
if (rc < 0) {
close(fd);
return NULL;
}
map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
close(fd);
if (map == MAP_FAILED)
return NULL;
if (size)
*size = st.st_size;
return map;
}
int main(int argc, char **argv)
{
char *fname;
void *map;
off_t map_len;
if (argc < 2) {
fprintf(stderr, "You must specify the file name of the ICE40-E1 capture\n");
exit(1);
}
fname = argv[1];
map = open_mmap_file(fname, &map_len);
if (!map) {
fprintf(stderr, "Error opening %s: %s\n", fname, strerror(errno));
exit(1);
}
process_file(map, map_len);
}