diff --git a/.checkpatch.conf b/.checkpatch.conf index 20c0832..b79575f 100644 --- a/.checkpatch.conf +++ b/.checkpatch.conf @@ -1,2 +1,3 @@ +--exclude ^software/e1-tracer/crc4itu.(c|h)$ --ignore OPEN_BRACE --ignore SPACING diff --git a/software/e1-tracer/Makefile b/software/e1-tracer/Makefile new file mode 100644 index 0000000..cc3c2cf --- /dev/null +++ b/software/e1-tracer/Makefile @@ -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 diff --git a/software/e1-tracer/crc4itu.c b/software/e1-tracer/crc4itu.c new file mode 100644 index 0000000..f13895a --- /dev/null +++ b/software/e1-tracer/crc4itu.c @@ -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 +#include + + + +/** + * 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; +} diff --git a/software/e1-tracer/crc4itu.h b/software/e1-tracer/crc4itu.h new file mode 100644 index 0000000..e19e696 --- /dev/null +++ b/software/e1-tracer/crc4itu.h @@ -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 +#include + +#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 */ diff --git a/software/e1-tracer/dump.c b/software/e1-tracer/dump.c new file mode 100644 index 0000000..9be8130 --- /dev/null +++ b/software/e1-tracer/dump.c @@ -0,0 +1,303 @@ +/* (C) 2019 by Harald Welte + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +//#include + +#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", ¬ify_cb, true, ls); + + snprintf(namebuf, sizeof(namebuf), "errplot-%d.dat", i); + ls->errplot.outfile = fopen(namebuf, "w"); + } + + process_file(rc); +} diff --git a/software/e1-tracer/dump_ts.py b/software/e1-tracer/dump_ts.py new file mode 100755 index 0000000..da865b1 --- /dev/null +++ b/software/e1-tracer/dump_ts.py @@ -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) diff --git a/software/e1-tracer/hdlc-decode-pipe.c b/software/e1-tracer/hdlc-decode-pipe.c new file mode 100644 index 0000000..3f69ccd --- /dev/null +++ b/software/e1-tracer/hdlc-decode-pipe.c @@ -0,0 +1,55 @@ +/* (C) 2020 by Harald Welte + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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); + } +} diff --git a/software/e1-tracer/idt82v2081.c b/software/e1-tracer/idt82v2081.c new file mode 100644 index 0000000..1dae2dc --- /dev/null +++ b/software/e1-tracer/idt82v2081.c @@ -0,0 +1,140 @@ +/* (C) 2019 by Harald Welte + * 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 + +#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; +} + diff --git a/software/e1-tracer/idt82v2081.h b/software/e1-tracer/idt82v2081.h new file mode 100644 index 0000000..1c930b1 --- /dev/null +++ b/software/e1-tracer/idt82v2081.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +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); diff --git a/software/e1-tracer/idt82v2081_regs.h b/software/e1-tracer/idt82v2081_regs.h new file mode 100644 index 0000000..679ed90 --- /dev/null +++ b/software/e1-tracer/idt82v2081_regs.h @@ -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 */ diff --git a/software/e1-tracer/idt82v2081_usb.c b/software/e1-tracer/idt82v2081_usb.c new file mode 100644 index 0000000..94ced63 --- /dev/null +++ b/software/e1-tracer/idt82v2081_usb.c @@ -0,0 +1,70 @@ +/* (C) 2019 by Harald Welte + * 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 +#include +#include + +#include + +#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; +} diff --git a/software/e1-tracer/idt82v2081_usb.h b/software/e1-tracer/idt82v2081_usb.h new file mode 100644 index 0000000..d04fdb2 --- /dev/null +++ b/software/e1-tracer/idt82v2081_usb.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +struct idt82; +struct libusb_device_handle; + +int idt82_usb_init(struct idt82 *idt, struct libusb_device_handle *devh, uint8_t ep); diff --git a/software/e1-tracer/main.c b/software/e1-tracer/main.c new file mode 100644 index 0000000..9d004a3 --- /dev/null +++ b/software/e1-tracer/main.c @@ -0,0 +1,451 @@ +/* (C) 2019 by Sylvain Munaut + * (C) 2019-2020 by Harald Welte + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#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; jppx; 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; jppx; 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; icount; 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; ientries[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; icount; i++) + { + xfr = libusb_alloc_transfer(flow->ppx); + if (!xfr) + return -ENOMEM; + + len = 0; + + if (flow->ep & 0x80) { + for (j=0; jppx; j++) + len += (xfr->iso_packet_desc[j].length = flow->size); + } else { + for (j=0; jppx; 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; +} diff --git a/software/e1-tracer/osmo_e1f.c b/software/e1-tracer/osmo_e1f.c new file mode 100644 index 0000000..4571c5d --- /dev/null +++ b/software/e1-tracer/osmo_e1f.c @@ -0,0 +1,710 @@ +/* Osmocom Software Defined E1 + * Implements ITU-T Rec. G.704 Section 2.3 + * + * (C) 2018 by Harald Welte + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include + +#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); +} diff --git a/software/e1-tracer/osmo_e1f.h b/software/e1-tracer/osmo_e1f.h new file mode 100644 index 0000000..1269728 --- /dev/null +++ b/software/e1-tracer/osmo_e1f.h @@ -0,0 +1,141 @@ +#pragma once + +/* Osmocom Software Defined E1 + * Implements ITU-T Rec. G.704 Section 2.3 + * + * (C) 2018 by Harald Welte + * 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 +#include +#include +#include +#include +#include + +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); diff --git a/software/e1-tracer/replay.c b/software/e1-tracer/replay.c new file mode 100644 index 0000000..6d32b9e --- /dev/null +++ b/software/e1-tracer/replay.c @@ -0,0 +1,173 @@ +/* (C) 2019 by Harald Welte + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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); +}