Harald Welte | f5e7264 | 2022-10-30 22:20:55 +0100 | [diff] [blame^] | 1 | /* (C) 2019 by Harald Welte <laforge@gnumonks.org> |
| 2 | * All Rights Reserved |
| 3 | * |
| 4 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published |
| 8 | * by the Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | */ |
| 16 | |
| 17 | #include <string.h> |
| 18 | #include <sys/types.h> |
| 19 | #include <fcntl.h> |
| 20 | #include <stdint.h> |
| 21 | #include <stdlib.h> |
| 22 | #include <getopt.h> |
| 23 | #include <unistd.h> |
| 24 | #include <stdio.h> |
| 25 | #include <errno.h> |
| 26 | #include <inttypes.h> |
| 27 | |
| 28 | #include <osmocom/core/utils.h> |
| 29 | #include <osmocom/core/logging.h> |
| 30 | #include <osmocom/core/application.h> |
| 31 | #include <osmocom/core/isdnhdlc.h> |
| 32 | #include <osmocom/core/gsmtap.h> |
| 33 | #include <osmocom/core/gsmtap_util.h> |
| 34 | //#include <osmocom/abis/lapd_pcap.h> |
| 35 | |
| 36 | #include "osmo_e1f.h" |
| 37 | |
| 38 | #define E1_CHUNK_HDR_MAGIC 0xe115600d /* E1 is good */ |
| 39 | struct e1_chunk_hdr { |
| 40 | uint32_t magic; |
| 41 | struct { |
| 42 | uint64_t sec; |
| 43 | uint64_t usec; |
| 44 | } time; |
| 45 | uint16_t len; /* length of following payload */ |
| 46 | uint8_t ep; /* USB endpoint */ |
| 47 | } __attribute__((packed)); |
| 48 | |
| 49 | |
| 50 | /* local state per E1 line */ |
| 51 | struct line_state { |
| 52 | unsigned int idx; |
| 53 | struct osmo_isdnhdlc_vars hdlc; |
| 54 | struct osmo_e1f_instance e1f; |
| 55 | struct { |
| 56 | FILE *outfile; |
| 57 | uint64_t last_sec; |
| 58 | uint64_t frame_err_cnt; |
| 59 | uint64_t crc_err_cnt; |
| 60 | bool ts0_crc4_error; |
| 61 | bool ts0_remote_alarm; |
| 62 | } errplot; |
| 63 | }; |
| 64 | |
| 65 | static struct line_state g_line[2]; /* one per direction */ |
| 66 | static int g_pcap_fd = -1; |
| 67 | static struct msgb *g_pcap_msg; |
| 68 | struct gsmtap_inst *g_gsmtap; |
| 69 | |
| 70 | /* called for each HDLC payload frame */ |
| 71 | static void handle_payload(uint8_t idx, const uint8_t *data, int len) |
| 72 | { |
| 73 | #if 0 |
| 74 | int dir; |
| 75 | |
| 76 | switch (idx) { |
| 77 | case 0: |
| 78 | dir = OSMO_LAPD_PCAP_INPUT; |
| 79 | break; |
| 80 | case 1: |
| 81 | dir = OSMO_LAPD_PCAP_OUTPUT; |
| 82 | break; |
| 83 | default: |
| 84 | fprintf(stderr, "Unexpected USB EP idx %d\n", idx); |
| 85 | return; |
| 86 | } |
| 87 | |
| 88 | if (g_pcap_fd >= 0) { |
| 89 | uint8_t *cur = msgb_put(g_pcap_msg, len); |
| 90 | memcpy(cur, data, len); |
| 91 | osmo_pcap_lapd_write(g_pcap_fd, dir, g_pcap_msg); |
| 92 | msgb_reset(g_pcap_msg); |
| 93 | } else |
| 94 | #endif |
| 95 | printf("%u: OUT: %s\n", idx, osmo_hexdump(data, len)); |
| 96 | |
| 97 | if (g_gsmtap) { |
| 98 | struct msgb *msg; |
| 99 | msg = gsmtap_makemsg_ex(GSMTAP_TYPE_E1T1, idx ? GSMTAP_ARFCN_F_UPLINK : 0, 255, |
| 100 | GSMTAP_E1T1_FR, 0, 0, 0, 0, data, len); |
| 101 | OSMO_ASSERT(msg); |
| 102 | gsmtap_sendmsg(g_gsmtap, msg); |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | |
| 107 | |
| 108 | static void handle_frame_errplot(struct line_state *ls, const struct e1_chunk_hdr *hdr, const uint8_t *data) |
| 109 | { |
| 110 | if (!ls->errplot.outfile) |
| 111 | return; |
| 112 | |
| 113 | if (!ls->errplot.last_sec) |
| 114 | ls->errplot.last_sec = hdr->time.sec; |
| 115 | |
| 116 | if (ls->errplot.last_sec != hdr->time.sec) { |
| 117 | /* dump the per-second total; start from 0 again */ |
| 118 | fprintf(ls->errplot.outfile, "%"PRIu64 " %"PRIu64 " %"PRIu64" %u %u\n", |
| 119 | hdr->time.sec, ls->errplot.frame_err_cnt, ls->errplot.crc_err_cnt, |
| 120 | ls->errplot.ts0_crc4_error, ls->errplot.ts0_remote_alarm); |
| 121 | ls->errplot.frame_err_cnt = 0; |
| 122 | ls->errplot.crc_err_cnt = 0; |
| 123 | ls->errplot.ts0_remote_alarm = false; |
| 124 | ls->errplot.ts0_crc4_error = false; |
| 125 | ls->errplot.last_sec = hdr->time.sec; |
| 126 | fflush(ls->errplot.outfile); |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | /* called for each USB transfer read from the file */ |
| 131 | static void handle_frame(const struct e1_chunk_hdr *hdr, const uint8_t *data) |
| 132 | { |
| 133 | uint8_t nots0[1024]; |
| 134 | unsigned int offs = 0; |
| 135 | struct line_state *ls; |
| 136 | |
| 137 | /* filter on the endpoint (direction) specified by the user */ |
| 138 | switch (hdr->ep) { |
| 139 | case 0x81: |
| 140 | ls = &g_line[0]; |
| 141 | break; |
| 142 | case 0x82: |
| 143 | ls = &g_line[1]; |
| 144 | break; |
| 145 | default: |
| 146 | fprintf(stderr, "Unexpected USB EP 0x%02x\n", hdr->ep); |
| 147 | return; |
| 148 | } |
| 149 | |
| 150 | if (hdr->len <= 4) |
| 151 | return; |
| 152 | |
| 153 | //printf("%u: %"PRIu64".%"PRIu64" EP=0x%02x\n", ls->idx, hdr->time.sec, hdr->time.usec, hdr->ep); |
| 154 | |
| 155 | OSMO_ASSERT(((hdr->len-4)/32)*31 < ARRAY_SIZE(nots0)); |
| 156 | /* gather the TS1..TS31 data, skipping TS0 */ |
| 157 | for (int i = 4; i < hdr->len-4; i += 32) { |
| 158 | //printf("%u:\t%s\n", ls->idx, osmo_hexdump(data+i, 32)); |
| 159 | memcpy(nots0+offs, data+i+1, 32-1); |
| 160 | offs += 31; |
| 161 | osmo_e1f_rx_frame(&ls->e1f, data+i); |
| 162 | } |
| 163 | |
| 164 | //printf("%u: IN(%u): %s\n", ls->idx, offs, osmo_hexdump(nots0, offs)); |
| 165 | uint8_t out[2048]; |
| 166 | int rc; |
| 167 | int rl; |
| 168 | |
| 169 | int oi = 0; |
| 170 | |
| 171 | while (oi < offs) { |
| 172 | rc = osmo_isdnhdlc_decode(&ls->hdlc, nots0+oi, offs-oi, &rl, out, sizeof(out)); |
| 173 | //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); |
| 174 | if (rc < 0) { |
| 175 | fprintf(stdout, "%u: ERR in HDLC decode: %d\n", ls->idx, rc); |
| 176 | if (rc == -1) |
| 177 | ls->errplot.frame_err_cnt++; |
| 178 | else if (rc == -2) |
| 179 | ls->errplot.crc_err_cnt++; |
| 180 | } else if (rc > 0) |
| 181 | handle_payload(ls->idx, out, rc); |
| 182 | oi += rl; |
| 183 | } |
| 184 | handle_frame_errplot(ls, hdr, data); |
| 185 | } |
| 186 | |
| 187 | static int process_file(int fd) |
| 188 | { |
| 189 | struct e1_chunk_hdr hdr; |
| 190 | unsigned long offset = 0; |
| 191 | uint8_t buf[65535]; |
| 192 | int rc; |
| 193 | |
| 194 | while (1) { |
| 195 | memset(buf, 0, sizeof(buf)); |
| 196 | /* first read header */ |
| 197 | rc = read(fd, &hdr, sizeof(hdr)); |
| 198 | if (rc < 0) |
| 199 | return rc; |
| 200 | if (rc != sizeof(hdr)) { |
| 201 | fprintf(stderr, "%d is less than header size (%zd)\n", rc, sizeof(hdr)); |
| 202 | return -1; |
| 203 | } |
| 204 | offset += rc; |
| 205 | if (hdr.magic != E1_CHUNK_HDR_MAGIC) { |
| 206 | fprintf(stderr, "offset %lu: Wrong magic 0x%08x\n", offset, hdr.magic); |
| 207 | return -1; |
| 208 | } |
| 209 | |
| 210 | /* then read payload */ |
| 211 | rc = read(fd, buf, hdr.len); |
| 212 | if (rc < 0) |
| 213 | return rc; |
| 214 | offset += rc; |
| 215 | if (rc != hdr.len) { |
| 216 | fprintf(stderr, "%d is less than payload size (%d)\n", rc, hdr.len); |
| 217 | return -1; |
| 218 | } |
| 219 | handle_frame(&hdr, buf); |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | static int open_file(const char *fname) |
| 224 | { |
| 225 | return open(fname, O_RDONLY); |
| 226 | } |
| 227 | |
| 228 | /* E1 framer notifies us of something */ |
| 229 | static void notify_cb(struct osmo_e1f_instance *e1i, enum osmo_e1f_notify_event evt, bool present, void *data) |
| 230 | { |
| 231 | struct line_state *ls = e1i->priv; |
| 232 | |
| 233 | printf("%u: NOTIFY: %s %s\n", ls->idx, osmo_e1f_notify_event_name(evt), |
| 234 | present ? "PRESENT" : "ABSENT"); |
| 235 | |
| 236 | if (present) { |
| 237 | switch (evt) { |
| 238 | case E1_NTFY_EVT_CRC_ERROR: |
| 239 | ls->errplot.ts0_crc4_error = present; |
| 240 | break; |
| 241 | case E1_NTFY_EVT_REMOTE_ALARM: |
| 242 | ls->errplot.ts0_remote_alarm = present; |
| 243 | break; |
| 244 | } |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | static const struct log_info_cat log_categories[] = { |
| 249 | }; |
| 250 | |
| 251 | static const struct log_info log_info = { |
| 252 | .cat = log_categories, |
| 253 | .num_cat = ARRAY_SIZE(log_categories), |
| 254 | }; |
| 255 | |
| 256 | int main(int argc, char **argv) |
| 257 | { |
| 258 | char *fname; |
| 259 | int rc; |
| 260 | int i; |
| 261 | |
| 262 | osmo_init_logging2(NULL, &log_info); |
| 263 | osmo_e1f_init(); |
| 264 | |
| 265 | if (argc < 2) { |
| 266 | fprintf(stderr, "You must specify the file name of the ICE40-E1 capture\n"); |
| 267 | exit(1); |
| 268 | } |
| 269 | fname = argv[1]; |
| 270 | |
| 271 | rc = open_file(fname); |
| 272 | if (rc < 0) { |
| 273 | fprintf(stderr, "Error opening %s: %s\n", fname, strerror(errno)); |
| 274 | exit(1); |
| 275 | } |
| 276 | |
| 277 | g_gsmtap = gsmtap_source_init("localhost", GSMTAP_UDP_PORT, 0); |
| 278 | gsmtap_source_add_sink(g_gsmtap); |
| 279 | |
| 280 | if (argc >= 3) { |
| 281 | #if 0 |
| 282 | g_pcap_fd = osmo_pcap_lapd_open(argv[2], 0640); |
| 283 | if (g_pcap_fd < 0) { |
| 284 | fprintf(stderr, "Unable to open PCAP output: %s\n", strerror(errno)); |
| 285 | exit(1); |
| 286 | } |
| 287 | g_pcap_msg = msgb_alloc(4096, "pcap"); |
| 288 | #endif |
| 289 | } |
| 290 | |
| 291 | for (i = 0; i < ARRAY_SIZE(g_line); i++) { |
| 292 | struct line_state *ls = &g_line[i]; |
| 293 | char namebuf[32]; |
| 294 | ls->idx = i; |
| 295 | osmo_isdnhdlc_rcv_init(&ls->hdlc, OSMO_HDLC_F_BITREVERSE); |
| 296 | osmo_e1f_instance_init(&ls->e1f, "dump", ¬ify_cb, true, ls); |
| 297 | |
| 298 | snprintf(namebuf, sizeof(namebuf), "errplot-%d.dat", i); |
| 299 | ls->errplot.outfile = fopen(namebuf, "w"); |
| 300 | } |
| 301 | |
| 302 | process_file(rc); |
| 303 | } |