diff --git a/.gitignore b/.gitignore index 14d9342..d7a0d68 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,7 @@ src/jtacs/jtacs src/r2000/radiocom2000 src/imts/imts src/imts/imts-dialer +src/mpt1327/mpt1327 src/jolly/jollycom src/eurosignal/eurosignal src/tv/osmotv diff --git a/README b/README index 66777d1..dca1cee 100644 --- a/README +++ b/README @@ -14,6 +14,7 @@ generated simultaniously using SDR. Currently supported networks: * JTACS (Japanese version of TACS) * Radiocom 2000 (French network) * IMTS / MTS ((Improved) Mobile Telephone Service) + * MPT1327 (Trunked Radio) aka known as 'Buendelfunk' * Eurosignal (ERuRD paging service) * JollyCom (Unofficial network, invented by the author) * C-Netz BSC (Connecting to a C-Netz Base Station) @@ -23,6 +24,7 @@ Additionally the following communication services are implemented: * TV Transmitter with test Images * Radio transmitter / receiver * Analog Modem Emulation (AM7911) + * German classic 'Zeitansage' (time announcement) USE AT YOUR OWN RISK! @@ -68,3 +70,4 @@ which seems not to exist anymore... Peter, Peter and Friedhelm and Stephan for providing documentation and hardware for C-Netz Base Station and other C-Netz documents. +Carsten Wollesen for donating MPT1327 radios and programming tools. diff --git a/configure.ac b/configure.ac index 239f453..65ccdc2 100644 --- a/configure.ac +++ b/configure.ac @@ -97,6 +97,7 @@ AC_OUTPUT( src/jtacs/Makefile src/r2000/Makefile src/imts/Makefile + src/mpt1327/Makefile src/jolly/Makefile src/eurosignal/Makefile src/tv/Makefile diff --git a/docs/index.html b/docs/index.html index 0f618d5..44ce6ae 100644 --- a/docs/index.html +++ b/docs/index.html @@ -108,6 +108,7 @@ Implemented networks:
  • AMPS - Advanced Mobile Phone Service (USA)
  • TACS / JTACS - Total Access Communication System (UK/Italy/Japan)
  • Radiocom 2000 (France)
  • +
  • MPT1327/Regionet43 (Bündelfunk) (Europe)
  • Eurosignal (Europe)
  • diff --git a/docs/mpt1327.html b/docs/mpt1327.html new file mode 100644 index 0000000..bfdd6cf --- /dev/null +++ b/docs/mpt1327.html @@ -0,0 +1,39 @@ + + + +osmocom-analog + + +
    + +

    MPT1327

    + +
    + +

    *this doc is under construction*

    + + + +

    + +History +

    + +

    + +How it works +

    + +

    + +Setup of a base station +

    + +
    [Back to main page]

    +
    + + diff --git a/src/Makefile.am b/src/Makefile.am index 3bd7136..344ffd7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -51,6 +51,7 @@ SUBDIRS += \ jtacs \ r2000 \ imts \ + mpt1327 \ jolly \ eurosignal \ tv \ diff --git a/src/jolly/Makefile.am b/src/jolly/Makefile.am index 346da7e..89ba1a5 100644 --- a/src/jolly/Makefile.am +++ b/src/jolly/Makefile.am @@ -16,7 +16,6 @@ jollycom_LDADD = \ $(top_builddir)/src/libmobile/libmobile.a \ $(top_builddir)/src/libosmocc/libosmocc.a \ $(top_builddir)/src/libdisplay/libdisplay.a \ - $(top_builddir)/src/libgoertzel/libgoertzel.a \ $(top_builddir)/src/libjitter/libjitter.a \ $(top_builddir)/src/libsquelch/libsquelch.a \ $(top_builddir)/src/libdtmf/libdtmf.a \ diff --git a/src/libdebug/debug.c b/src/libdebug/debug.c index 07f7b12..44902ea 100755 --- a/src/libdebug/debug.c +++ b/src/libdebug/debug.c @@ -50,6 +50,7 @@ struct debug_cat { { "amps", "\033[1;34m" }, { "r2000", "\033[1;34m" }, { "imts", "\033[1;34m" }, + { "mpt1327", "\033[1;34m" }, { "jollycom", "\033[1;34m" }, { "eurosignal", "\033[1;34m" }, { "frame", "\033[0;36m" }, diff --git a/src/mpt1327/Makefile.am b/src/mpt1327/Makefile.am new file mode 100644 index 0000000..ed0341e --- /dev/null +++ b/src/mpt1327/Makefile.am @@ -0,0 +1,47 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +bin_PROGRAMS = \ + mpt1327 + +mpt1327_SOURCES = \ + mpt1327.c \ + dsp.c \ + message.c \ + main.c +mpt1327_LDADD = \ + $(COMMON_LA) \ + ../anetz/libgermanton.a \ + $(top_builddir)/src/liboptions/liboptions.a \ + $(top_builddir)/src/libdebug/libdebug.a \ + $(top_builddir)/src/libmobile/libmobile.a \ + $(top_builddir)/src/libosmocc/libosmocc.a \ + $(top_builddir)/src/libdisplay/libdisplay.a \ + $(top_builddir)/src/libjitter/libjitter.a \ + $(top_builddir)/src/libsquelch/libsquelch.a \ + $(top_builddir)/src/libdtmf/libdtmf.a \ + $(top_builddir)/src/libtimer/libtimer.a \ + $(top_builddir)/src/libsamplerate/libsamplerate.a \ + $(top_builddir)/src/libemphasis/libemphasis.a \ + $(top_builddir)/src/libfsk/libfsk.a \ + $(top_builddir)/src/libfm/libfm.a \ + $(top_builddir)/src/libfilter/libfilter.a \ + $(top_builddir)/src/libwave/libwave.a \ + $(top_builddir)/src/libsample/libsample.a \ + $(top_builddir)/src/libg711/libg711.a \ + -lm + +if HAVE_ALSA +mpt1327_LDADD += \ + $(top_builddir)/src/libsound/libsound.a \ + $(ALSA_LIBS) +endif + +if HAVE_SDR +mpt1327_LDADD += \ + $(top_builddir)/src/libsdr/libsdr.a \ + $(top_builddir)/src/libam/libam.a \ + $(top_builddir)/src/libfft/libfft.a \ + $(UHD_LIBS) \ + $(SOAPY_LIBS) +endif + diff --git a/src/mpt1327/dsp.c b/src/mpt1327/dsp.c new file mode 100644 index 0000000..4b6b7dc --- /dev/null +++ b/src/mpt1327/dsp.c @@ -0,0 +1,349 @@ +/* audio processing + * + * (C) 2021 by Andreas Eversberg + * All Rights Reserved + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define CHAN mpt1327->sender.kanal + +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libmobile/call.h" +#include "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "mpt1327.h" +#include "dsp.h" +#include "message.h" + +#define PI M_PI + +/* signaling */ +#define MAX_DEVIATION 2500.0 +#define MAX_MODULATION 2550.0 +#define SPEECH_DEVIATION 1500.0 /* deviation of speech (no emphasis) */ +#define TX_PEAK_FSK (1500.0 / SPEECH_DEVIATION) +#define BIT_RATE 1200.0 +#define BIT_ADJUST 0.1 /* how much do we adjust bit clock on frequency change */ +#define F0 1800.0 +#define F1 1200.0 +#define MAX_DISPLAY 1.4 /* something above speech level */ + +/* carrier loss detection */ +#define MUTE_TIME 0.1 /* time to mute after loosing signal */ + +void dsp_init(void) +{ +} + +static int fsk_send_bit(void *inst); +static void fsk_receive_bit(void *inst, int bit, double quality, double level); + +/* Init FSK of transceiver */ +int dsp_init_sender(mpt1327_t *mpt1327, double squelch_db) +{ + int rc; + + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for Transceiver.\n"); + + /* init squelch */ + squelch_init(&mpt1327->squelch, mpt1327->sender.kanal, squelch_db, MUTE_TIME, MUTE_TIME); + + /* set modulation parameters */ + sender_set_fm(&mpt1327->sender, MAX_DEVIATION, MAX_MODULATION, SPEECH_DEVIATION, MAX_DISPLAY); + + PDEBUG(DDSP, DEBUG_DEBUG, "Using FSK level of %.3f (%.3f KHz deviation)\n", TX_PEAK_FSK, SPEECH_DEVIATION * TX_PEAK_FSK / 1e3); + + /* init fsk */ + if (fsk_mod_init(&mpt1327->fsk_mod, mpt1327, fsk_send_bit, mpt1327->sender.samplerate, BIT_RATE, F0, F1, TX_PEAK_FSK, 1, 0) < 0) { + PDEBUG_CHAN(DDSP, DEBUG_ERROR, "FSK init failed!\n"); + return -EINVAL; + } + if (fsk_demod_init(&mpt1327->fsk_demod, mpt1327, fsk_receive_bit, mpt1327->sender.samplerate, BIT_RATE, F0, F1, BIT_ADJUST) < 0) { + PDEBUG_CHAN(DDSP, DEBUG_ERROR, "FSK init failed!\n"); + return -EINVAL; + } + + mpt1327->dmp_frame_level = display_measurements_add(&mpt1327->sender.dispmeas, "Frame Level", "%.1f %% (last)", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0); + mpt1327->dmp_frame_quality = display_measurements_add(&mpt1327->sender.dispmeas, "Frame Quality", "%.1f %% (last)", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 100.0, 100.0); + + /* repeater */ + rc = jitter_create(&mpt1327->repeater_dejitter, mpt1327->sender.samplerate / 5); + if (rc < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "Failed to create and init repeater buffer!\n"); + goto error; + } + return 0; + +error: + dsp_cleanup_sender(mpt1327); + return rc; +} + +/* Cleanup transceiver instance. */ +void dsp_cleanup_sender(mpt1327_t *mpt1327) +{ + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for Transceiver.\n"); + + fsk_mod_cleanup(&mpt1327->fsk_mod); + fsk_demod_cleanup(&mpt1327->fsk_demod); + + jitter_destroy(&mpt1327->repeater_dejitter); +} + +/* Check for SYNC bits, then collect data bits */ +static void fsk_receive_bit(void *inst, int bit, double quality, double level) +{ + mpt1327_t *mpt1327 = (mpt1327_t *)inst; + int i; + + /* normalize FSK level */ + level /= TX_PEAK_FSK; + +// printf("bit=%d quality=%.4f\n", bit, quality); + if (!mpt1327->rx_in_sync) { + mpt1327->rx_sync = (mpt1327->rx_sync << 1) | bit; + + /* level and quality */ + mpt1327->rx_level[mpt1327->rx_count & 0xff] = level; + mpt1327->rx_quality[mpt1327->rx_count & 0xff] = quality; + mpt1327->rx_count++; + + /* check if sync pattern match */ + if (mpt1327->rx_sync != mpt1327->sync_word) + return; + + /* average level and quality */ + level = quality = 0; + for (i = 0; i < 16; i++) { + level += mpt1327->rx_level[(mpt1327->rx_count - 1 - i) & 0xff]; + quality += mpt1327->rx_quality[(mpt1327->rx_count - 1 - i) & 0xff]; + } + level /= 16.0; quality /= 16.0; +// printf("sync (level = %.2f, quality = %.2f\n", level, quality); + + /* do not accept garbage */ + if (quality < 0.65) + return; + + /* rest sync register */ + mpt1327->rx_sync = 0; + mpt1327->rx_in_sync = 1; + mpt1327->rx_count = 0; + + /* mute audio from now on */ + mpt1327->rx_mute = 1; + + return; + } + + /* read bits */ + mpt1327->rx_bits = (mpt1327->rx_bits << 1) | (bit & 1); + mpt1327->rx_level[mpt1327->rx_count] = level; + mpt1327->rx_quality[mpt1327->rx_count] = quality; + if (++mpt1327->rx_count != 64) + return; + + /* check parity */ + if (mpt1327_checkbits(mpt1327->rx_bits, NULL) != (mpt1327->rx_bits & 0xffff)) { + PDEBUG(DDSP, DEBUG_NOTICE, "Received corrupt codeword or noise.\n"); + mpt1327->rx_in_sync = 0; + mpt1327->rx_mute = 0; + return; + } + + /* reset counter for next frame */ + mpt1327->rx_count = 0; + + /* average level and quality */ + level = quality = 0; + for (i = 0; i < 64; i++) { + level += mpt1327->rx_level[i]; + quality += mpt1327->rx_quality[i]; + } + level /= 64.0; quality /= 64.0; + + /* update measurements */ + display_measurements_update(mpt1327->dmp_frame_level, level * 100.0, 0.0); + display_measurements_update(mpt1327->dmp_frame_quality, quality * 100.0, 0.0); + + /* convert level so that received level at TX_PEAK_FSK results in 1.0 (100%) */ + mpt1327_receive_codeword(mpt1327, mpt1327->rx_bits, quality, level); +} + +/* Process received audio stream from radio unit. */ +void sender_receive(sender_t *sender, sample_t *samples, int length, double __attribute__((unused)) rf_level_db) +{ + mpt1327_t *mpt1327 = (mpt1327_t *) sender; + sample_t *spl; + int pos; + int i; + int was_mute = mpt1327->rx_mute; /* remember, so always mute whole chunk */ + int was_pressel_on = mpt1327->pressel_on; + + /* if channel is off, do nothing */ + if (mpt1327->dsp_mode == DSP_MODE_OFF) { + /* measure squelch even if channel is turned off */ + if (!isinf(mpt1327->squelch.threshold_db)) + squelch(&mpt1327->squelch, rf_level_db, (double)length / (double)mpt1327->sender.samplerate); + return; + } + + /* fsk signal */ + fsk_demod_receive(&mpt1327->fsk_demod, samples, length); + + /* on traffic channel mute and indicate signal strength */ + if (mpt1327->dsp_mode == DSP_MODE_TRAFFIC) { + /* process signal mute/loss, also for signalling tone */ + if (!isinf(mpt1327->squelch.threshold_db)) { + /* use squelch to unmute and reset call timer */ + switch (squelch(&mpt1327->squelch, rf_level_db, (double)length / (double)mpt1327->sender.samplerate)) { + case SQUELCH_LOSS: + case SQUELCH_MUTE: + memset(samples, 0, sizeof(*samples) * length); + break; + default: + mpt1327_signal_indication(mpt1327); + } + } else { + /* muting audio while pressel is off */ + if (!was_pressel_on || !mpt1327->pressel_on) + memset(samples, 0, sizeof(*samples) * length); + } + /* muting audio while receiving frame */ + if (was_mute || mpt1327->rx_mute) + memset(samples, 0, sizeof(*samples) * length); + } + + if (mpt1327->dsp_mode == DSP_MODE_TRAFFIC) { + /* if repeater mode, store sample in jitter buffer */ + if (mpt1327->repeater) + jitter_save(&mpt1327->repeater_dejitter, samples, length); + + if (mpt1327->unit && mpt1327->unit->callref) { + int count; + + count = samplerate_downsample(&mpt1327->sender.srstate, samples, length); + spl = mpt1327->sender.rxbuf; + pos = mpt1327->sender.rxbuf_pos; + for (i = 0; i < count; i++) { + spl[pos++] = samples[i]; + if (pos == 160) { + call_up_audio(mpt1327->unit->callref, spl, 160); + pos = 0; + } + } + mpt1327->sender.rxbuf_pos = pos; + } else + mpt1327->sender.rxbuf_pos = 0; + } else + mpt1327->sender.rxbuf_pos = 0; +} + +static int fsk_send_bit(void *inst) +{ + mpt1327_t *mpt1327 = (mpt1327_t *)inst; + + /* send frame bit (prio) */ + if (!mpt1327->tx_bit_num || mpt1327->tx_count == mpt1327->tx_bit_num) { + /* request frame */ + mpt1327->tx_bit_num = mpt1327_send_codeword(mpt1327, &mpt1327->tx_bits); + if (mpt1327->tx_bit_num == 0) { + return -1; + } + mpt1327->tx_count = 0; + } + return (mpt1327->tx_bits >> (63 - mpt1327->tx_count++)) & 1; + + return -1; +} + +/* Provide stream of audio toward radio unit */ +void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length) +{ + mpt1327_t *mpt1327 = (mpt1327_t *) sender; + + if (mpt1327->dsp_mode == DSP_MODE_OFF) { + memset(power, 0, length); + memset(samples, 0, sizeof(*samples) * length); + return; + } + + memset(power, 1, length); + + if (mpt1327->dsp_mode == DSP_MODE_TRAFFIC) { + jitter_load(&mpt1327->sender.dejitter, samples, length); + /* if repeater mode, sum samples from jitter buffer to samples */ + if (mpt1327->repeater) { + sample_t uplink[length]; + int i; + jitter_load(&mpt1327->repeater_dejitter, uplink, length); + for (i = 0; i < length; i++) + samples[i] += uplink[i]; + } + } else + memset(samples, 0, sizeof(*samples) * length); + + /* If there is something to modulate (pending TX frame), + * overwrite audio with FSK audio. */ + fsk_mod_send(&mpt1327->fsk_mod, samples, length, 0); +} + +const char *mpt1327_dsp_mode_name(enum dsp_mode mode) +{ + static char invalid[16]; + + switch (mode) { + case DSP_MODE_OFF: + return "OFF"; + case DSP_MODE_TRAFFIC: + return "TRAFFIC"; + case DSP_MODE_CONTROL: + return "CONTROL"; + } + + sprintf(invalid, "invalid(%d)", mode); + return invalid; +} + +void mpt1327_set_dsp_mode(mpt1327_t *mpt1327, enum dsp_mode mode, int repeater) +{ + //NOTE: DO NOT RESET FRAME, because mode may change before frame has been sent! + + if (mode == DSP_MODE_CONTROL) + mpt1327->sync_word = 0xc4d7; + if (mode == DSP_MODE_TRAFFIC) + mpt1327->sync_word = 0x3b28; + + if (repeater) + jitter_reset(&mpt1327->repeater_dejitter); + mpt1327->repeater = repeater; + + PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "DSP mode %s -> %s\n", mpt1327_dsp_mode_name(mpt1327->dsp_mode), mpt1327_dsp_mode_name(mode)); + mpt1327->dsp_mode = mode; +} + +void mpt1327_reset_sync(mpt1327_t *mpt1327) +{ + mpt1327->rx_in_sync = 0; + mpt1327->rx_sync = 0; + mpt1327->rx_mute = 0; +} + diff --git a/src/mpt1327/dsp.h b/src/mpt1327/dsp.h new file mode 100644 index 0000000..5352958 --- /dev/null +++ b/src/mpt1327/dsp.h @@ -0,0 +1,7 @@ + +void dsp_init(void); +int dsp_init_sender(mpt1327_t *mpt1327, double squelch_db); +void dsp_cleanup_sender(mpt1327_t *mpt1327); +void mpt1327_set_dsp_mode(mpt1327_t *mpt1327, enum dsp_mode mode, int repeater); +void mpt1327_reset_sync(mpt1327_t *mpt1327);; + diff --git a/src/mpt1327/main.c b/src/mpt1327/main.c new file mode 100644 index 0000000..bd90f4d --- /dev/null +++ b/src/mpt1327/main.c @@ -0,0 +1,398 @@ +/* MPT1327 main + * + * (C) 2021 by Andreas Eversberg + * All Rights Reserved + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libmobile/main_mobile.h" +#include "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "../anetz/freiton.h" +#include "../anetz/besetztton.h" +#include "../liboptions/options.h" +#include "mpt1327.h" +#include "dsp.h" +#include "message.h" + +/* settings */ +int num_freq = 0; +static int num_chan_type = 0; +static double squelch_db = -INFINITY; +static enum mpt1327_band band = BAND_REGIONET43_SUB1; +static enum mpt1327_chan_type chan_type[MAX_SENDER] = { CHAN_TYPE_CC_TC }; +static int16_t sys = -1; +static int wt = 10; +static int per = 5; +static int pon = 1; +static int timeout = 30; + +void print_image(void) {} + +void print_help(const char *arg0) +{ + main_mobile_print_help(arg0, "-O ... | -I ... "); + /* - - */ + printf(" -B --band | list\n"); + printf(" Select frequency Band (default = '%s')\n", mpt1327_band_name(band)); + printf(" -T --channel-type | list\n"); + printf(" Give channel type, use 'list' to get a list. (default = '%s')\n", chan_type_short_name(chan_type[0])); + printf(" -O --operator \n"); + printf(" -> decimal, '0x' for hex or all binary digits\n"); + printf(" Give System Identity Code of regional network (1st bit = 0)\n"); + printf(" OPID: Operator Identity (7 binary digits)\n"); + printf(" -> Check subscription data of mobile unit\n"); + printf(" NDD: Network Dependent Data (4 binary digts)\n"); + printf(" -> Check subscription data of mobile unit (must be '0001' or greater)\n"); + printf(" -> Change it to force re-registering of mobile unit.\n"); + printf(" LAB: Label for multiple control channels (3 binary digits)\n"); + printf(" -> Use '001' to allow all categories\n"); + printf(" -N --net \n"); + printf(" -> decimal, '0x' for hex or all binary digits\n"); + printf(" Give System Identity Code of national network (1st bit = 1)\n"); + printf(" NET: Network Identity (2 binary digits)\n"); + printf(" -> Check subscription data of mobile unit (must be '000000001' or greater)\n"); + printf(" -> Change it to force re-registering of mobile unit.\n"); + printf(" NDD: Network Dependent Data (9 binary digts)\n"); + printf(" LAB: Label for multiple control channels (3 binary digits)\n"); + printf(" -> Use '001' to allow all categories\n"); + printf(" -S --sysdef wt=5 | wt=10 | wt=15\n"); + printf(" Number of slots the Radio Unit waits for response. A slot lasts about\n"); + printf(" 107 ms. (default = %d)\n", wt); + printf(" -S --sysdef per= | per=0\n"); + printf(" Interval of periodic messages from the Radio Unit while transmitting\n"); + printf(" speech. Use 1..31 to enable and 0 to disable. Also the 'timeout' value\n"); + printf(" must be greater than value given here. (default = %d)\n", per); + printf(" -S --sysdef pon=1 | pon=0\n"); + printf(" The Radio Unit must send 'Pressel On' message to unmute the uplink.\n"); + printf(" If disabled, squelch must be enabled. (default = %d)\n", pon); + printf(" -S --sysdef timeout= | timeout=off\n"); + printf(" The Traffic Channel is released, if no radio transmits for given amount of time.\n"); + printf(" (default = %d)\n", timeout); + printf(" -Q --squelch | auto\n"); + printf(" Use given RF level to detect transmission on Traffic Channel, if\n"); + printf(" 'Pressel On' is disabled.\n"); + printf(" and stays below this level, the connection is released.\n"); + printf(" Use 'auto' to do automatic noise floor calibration to detect loss.\n"); + printf(" Only works with SDR! (disabled by default)\n"); + + printf("\nstation-id: Give 7 digits of Radio Unit's prefix/ident, you don't need to\n"); + printf(" enter it for every start of this program.\n"); + main_mobile_print_hotkeys(); + printf("Press 'i' key to dump list of seen Radio Units.\n"); +} + +static void add_options(void) +{ + main_mobile_add_options(); + option_add('B', "band", 1); + option_add('T', "channel-type", 1); + option_add('O', "operator", 3); + option_add('N', "net", 3); + option_add('S', "sysdef", 1); + option_add('Q', "squelch", 1); +} + +static int read_sys(const char *param, const char *value, int digits) +{ + int result = 0; + int i; + + if ((int)strlen(value) < digits) { + result = strtoul(value, NULL, 0); + if (result >= (1 << digits)) { + fprintf(stderr, "Given '%s' value is out of range for %d binary digits, use '-h' for help!\n", param, digits); + return -EINVAL; + } + return result; + } + + if ((int)strlen(value) > digits) { + fprintf(stderr, "Given '%s' value must have exactly %d binary digits, use '-h' for help!\n", param, digits); + return -EINVAL; + } + + for (i = 0; i < (int)strlen(value); i++) { + if (value[i] < '0' || value[i] > '1') { + fprintf(stderr, "Given '%s' value must only have binary digits of '0' or '1', use '-h' for help!\n", param); + return -EINVAL; + } + result = (result << 1) | (value[i] - '0'); + } + + return result; +} + +static int handle_options(int short_option, int argi, char **argv) +{ + int rc; + const char *p; + + switch (short_option) { + case 'B': + if (!strcmp(argv[argi], "list")) { + mpt1327_band_list(); + return 0; + } + rc = mpt1327_band_by_short_name(argv[argi]); + if (rc < 0) { + fprintf(stderr, "Given band '%s' is illegal, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + band = rc; + break; + case 'T': + if (!strcmp(argv[argi], "list")) { + mpt1327_channel_list(); + return 0; + } + rc = mpt1327_channel_by_short_name(argv[argi]); + if (rc < 0) { + fprintf(stderr, "Error, channel type '%s' unknown. Please use '-t list' to get a list. I suggest to use the default.\n", argv[argi]); + return -EINVAL; + } + OPT_ARRAY(num_chan_type, chan_type, rc) + break; + case 'O': + sys = 0x0000; + rc = read_sys("OID", argv[argi + 0], 7); + if (rc < 0) + return rc; + sys = sys | (rc << 7); + rc = read_sys("NDD", argv[argi + 1], 4); + if (rc < 0) + return rc; + sys = sys | (rc << 3); + rc = read_sys("LAB", argv[argi + 2], 3); + if (rc < 0) + return rc; + sys = sys | rc; + break; + case 'N': + sys = 0x4000; + rc = read_sys("NET", argv[argi + 0], 2); + if (rc < 0) + return rc; + sys = sys | (rc << 12); + rc = read_sys("NDD", argv[argi + 1], 9); + if (rc < 0) + return rc; + sys = sys | (rc << 3); + rc = read_sys("LAB", argv[argi + 2], 3); + if (rc < 0) + return rc; + sys = sys | rc; + break; + case 'S': + p = strchr(argv[argi], '='); + if (!p) { + fprintf(stderr, "Given sysdef parameter '%s' requires '=' character to set value, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + p++; + if (!strncasecmp(argv[argi], "wt=", p - argv[argi])) { + wt = atoi(p); + if (wt != 5 && wt != 10 && wt != 15) { +sysdef_oor: + fprintf(stderr, "Given sysdef parameter '%s' out of range, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + } else + if (!strncasecmp(argv[argi], "per=", p - argv[argi])) { + per = atoi(p); + if (per < 0 || per >31) + goto sysdef_oor; + } else + if (!strncasecmp(argv[argi], "pon=", p - argv[argi])) { + pon = atoi(p); + if (pon != 0 && pon != 1) + goto sysdef_oor; + } else + if (!strncasecmp(argv[argi], "timeout=", p - argv[argi])) { + timeout = atoi(p); + } else + { + fprintf(stderr, "Given sysdef parameter '%s' unknown, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + break; + case 'Q': + if (!strcasecmp(argv[argi], "auto")) + squelch_db = 0.0; + else + squelch_db = atof(argv[argi]); + break; + default: + return main_mobile_handle_options(short_option, argi, argv); + } + + return 1; +} + +int main(int argc, char *argv[]) +{ + int rc, argi; + const char *station_id = ""; + int mandatory = 0; + int i; + + /* init tones */ + init_freiton(); + init_besetzton(); +// init_ansage(); + + console_digits = "0123456789*#"; + main_mobile_init(); + + /* handle options / config file */ + add_options(); + rc = options_config_file(argc, argv, "~/.osmocom/analog/mpt1327.conf", handle_options); + if (rc < 0) + return 0; + argi = options_command_line(argc, argv, handle_options); + if (argi <= 0) + return argi; + + if (argi < argc) { + station_id = argv[argi]; + if (strlen(station_id) != 7) { + printf("Given station ID '%s' does not have 4 digits\n", station_id); + return 0; + } + } + + if (!num_kanal) { + printf("No channel (\"Kanal\") is specified, I suggest channel 1.\n\n"); + mandatory = 1; + } + if (use_sdr) { + /* set audiodev */ + for (i = 0; i < num_kanal; i++) + audiodev[i] = "sdr"; + num_audiodev = num_kanal; + /* set channel types for more than 1 channel */ + if (num_kanal > 1 && num_chan_type == 0) { + chan_type[0] = CHAN_TYPE_CC; + for (i = 1; i < num_kanal; i++) + chan_type[i] = CHAN_TYPE_TC; + num_chan_type = num_kanal; + } + + } + if (num_kanal == 1 && num_audiodev == 0) + num_audiodev = 1; /* use default */ + if (num_kanal != num_audiodev) { + fprintf(stderr, "You need to specify as many sound devices as you have channels.\n"); + exit(0); + } + if (num_kanal == 1 && num_chan_type == 0) + num_chan_type = 1; /* use default */ + if (num_kanal != num_chan_type) { + fprintf(stderr, "You need to specify as many channel types as you have channels.\n"); + exit(0); + } + + if (sys < 0) { + fprintf(stderr, "No System Identity Code is specified, make them match with your radio unit.\n\n"); + mandatory = 1; + } + if (isinf(squelch_db) && pon == 0) { + fprintf(stderr, "'Pressel On' message (PON) and squelch are turned off. Enable one of them.\n\n"); + mandatory = 1; + } + if (!isinf(squelch_db) && pon == 1) { + fprintf(stderr, "'Pressel On' message (PON) and squelch are turned on. Disable one of them.\n\n"); + mandatory = 1; + } + if (pon && timeout <= per) { + fprintf(stderr, "The defined timeout value is lower than the Periodic message interval (PER). Define a greater timeout.\n\n"); + mandatory = 1; + } + if (pon && (timeout && !per)) { + fprintf(stderr, "You must enable Periodic message interval (PER), if you use timeout (and have no squelch).\n\n"); + mandatory = 1; + } + if (!pon && !timeout) { + fprintf(stderr, "Warning: 'Pressel On' message (PON) and timeout is both disabled. There will be no way to detect loss of Radio Unit.\n\n"); + } + + if (do_de_emphasis || do_pre_emphasis) { + printf("Don't use pre-/de-emphasis, it is not used for Speech, nor for signaling.\n\n"); + mandatory = 1; + } + + if (mandatory) { + print_help(argv[0]); + return 0; + } + + /* no SDR, no squelch */ + if (!use_sdr && !isinf(squelch_db)) { + fprintf(stderr, "Cannot use squelch without SDR! Analog receivers don't give use RSSI.\n"); + goto fail; + } + + printf("Using Sysdef 0x%04x:\n", sys); + if (!(sys & 0x4000)) { + printf("OID=%d NDD=%d LAB=%d\n", (sys >> 7) & 0x7f, (sys >> 3) & 0xf, sys & 0x7); + } else { + printf("NET=%d NDD=%d LAB=%d\n", (sys >> 12) & 0x3, (sys >> 3) & 0x1ff, sys & 0x7); + } + + /* inits */ + fm_init(fast_math); + dsp_init(); + init_codeword(); + init_sysdef(sys, wt, per, pon, timeout); + + /* create transceiver instance */ + for (i = 0; i < num_kanal; i++) { + rc = mpt1327_create(band, kanal[i], chan_type[i], audiodev[i], use_sdr, samplerate, rx_gain, tx_gain, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, squelch_db); + if (rc < 0) { + fprintf(stderr, "Failed to create transceiver instance. Quitting!\n"); + goto fail; + } + printf("base station on channel %s ready, please tune transmitter to %.4f MHz and receiver to %.4f MHz. (%s %.3f MHz offset)\n", kanal[i], mpt1327_channel2freq(band, atoi(kanal[i]), 0) / 1e6, mpt1327_channel2freq(band, atoi(kanal[i]), 1) / 1e6, mpt1327_band_name(band), mpt1327_channel2freq(band, atoi(kanal[i]), 2) / 1e6); + } + + mpt1327_check_channels(); + + main_mobile("mpt1327", &quit, latency, interval, NULL, station_id, 7); + +fail: + /* destroy transceiver instance */ + while (sender_head) + mpt1327_destroy(sender_head); + + /* exits */ + fm_exit(); + flush_units(); + + options_free(); + + return 0; +} + diff --git a/src/mpt1327/message.c b/src/mpt1327/message.c new file mode 100644 index 0000000..c850594 --- /dev/null +++ b/src/mpt1327/message.c @@ -0,0 +1,566 @@ +/* message transcoding + * + * (C) 2021 by Andreas Eversberg + * All Rights Reserved + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "message.h" + +static struct mpt1327_parameter_names { + const char *name; + const char *description; +} mpt1327_parameter_names[] = { + { "", "Constant" }, + { "PFIX", "Group Prefix" }, + { "IDENT1", "Called Party Number" }, + { "D", "Data Call" }, + { "CHAN", "Channel Number" }, + { "IDENT2", "Calling Party Number" }, + { "(N)", "Aloha Number" }, + { "P", "Parity" }, + { "CAT", "Category" }, + { "TYPE", "Type" }, + { "FUNC", "Function" }, + { "CHAN4", "Last 4 Bits of Channel Number" }, + { "WT", "Delay Parameter" }, + { "RSVD", "Reserved" }, + { "(M)", "Address Qualifier" }, + { "QUAL", "Qualifies FUNC" }, + { "DT", "Data" }, + { "LEVEL", "Priority level" }, + { "EXT", "Extended Addressing" }, + { "FLAG1", "Flag 1" }, + { "FLAG2", "Flag 2" }, + { "PARAMETERS", "Parameters" }, + { "SD", "Speech and/or Data" }, + { "DIV", "Diversion" }, + { "INFO", "Info" }, + { "STATUS", "Status" }, + { "SLOTS", "Slots for Data Message" }, + { "POINT", "Demand Acknowledgement" }, + { "CHECK", "Availability Check" }, + { "E", "Emergency Call" }, + { "AD", "Data is appended" }, + { "DESC", "Type of Data" }, + { "A", "B" }, + { "B", "B" }, + { "SPARE", "Spare" }, + { "REVS", "Bit Reversals" }, + { "OPER", NULL }, + { "SYS", NULL }, + { "CONT", NULL }, + { "SYSDEF", NULL }, + { "PER", NULL }, + { "IVAL", NULL }, + { "PON", NULL }, + { "ID", NULL }, + { "ADJSITE", NULL }, + { "SOL", NULL }, + { "LEN", NULL }, + { "PREFIX2", NULL }, + { "KIND", NULL }, + { "PORT", NULL }, + { "FAD", NULL }, + { "INTER", NULL }, + { "HADT", NULL }, + { "MODEM", NULL }, + { "O/R", NULL }, + { "RATE", NULL }, + { "TRANS", NULL }, + { "RNITEL", NULL }, + { "TNITEL", NULL }, + { "JOB", NULL }, + { "REASON", NULL }, + { "ATRANS", NULL }, + { "EFLAGS", NULL }, + { "TASK", NULL }, + { "ONES", NULL }, + { "ITENUM", NULL }, + { "USERDATA", NULL }, + { "I/G", NULL }, + { "MORE", NULL }, + { "LASTBIT", NULL }, + { "FRAGL", NULL }, + { "RTRANS", NULL }, + { "W/F", NULL }, + { "P/N", NULL }, + { "DN", NULL }, + { "SPRE", NULL }, + { "SX", NULL }, + { "CAUSE", NULL }, + { "I/T", NULL }, + { "RESP", NULL }, + { "TOC", NULL }, + { "CCS", "Codeword Completion Sequence" }, + { "LET", "Link Establishmen Time" }, + { "PREAMBLE", NULL }, + { "PARAMETERS1", NULL }, + { "PARAMETERS2", NULL }, + { "BCD11", "11 Digits encoded as BCD" }, + { "RSA", NULL }, + { "FCW", NULL }, + { "SP", NULL }, + { "EXCHANGE", NULL }, + { "Number", NULL }, + { "GF", NULL }, + { "PFIXT", NULL }, + { "IDENTT", NULL }, + { "FORM", NULL }, + { "PFIX2", NULL }, +}; + +char *mpt1327_bcd = "0123456789R*#RR"; /* last digit is NULL */ + +static struct definitions { + int specific_only; + enum mpt1327_codeword_dir dir; + enum mpt1327_codeword_type type; + char *def; + const char *short_name; + const char *long_name; +} definitions[] = { + /* Filler */ + { 1, MPT_DOWN, MPT_FILLER, "0 RSVD:47=00000000000000000000000000000000000000000000000 P:16", "filler", "Filler Data" }, + /* GTC Message */ + { 0, MPT_DOWN, MPT_GTC, "1 PFIX:7 IDENT1:13 0 D:1 CHAN:10 IDENT2:13 (N):2 P:16", "GTC", "Go To Traffic Channel" }, + /* Category '000' Messages: Aloha Messages (Type '00') */ + { 0, MPT_DOWN, MPT_ALH, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=00 FUNC:3=000 CHAN4:4 WT:3 RSVD:2 (M):5 (N):4 P:16", "ALH", "Aloha: Any single codeword message invited" }, + { 0, MPT_DOWN, MPT_ALHS, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=00 FUNC:3=001 CHAN4:4 WT:3 RSVD:2 (M):5 (N):4 P:16", "ALHS", "Aloha: Messages invited, except RQD" }, + { 0, MPT_DOWN, MPT_ALHD, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=00 FUNC:3=010 CHAN4:4 WT:3 RSVD:2 (M):5 (N):4 P:16", "ALHD", "Aloha: Messages invited, except RQS" }, + { 0, MPT_DOWN, MPT_ALHE, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=00 FUNC:3=011 CHAN4:4 WT:3 RSVD:2 (M):5 (N):4 P:16", "ALHE", "Aloha: Emergency requests (RQE) only invited" }, + { 0, MPT_DOWN, MPT_ALHR, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=00 FUNC:3=100 CHAN4:4 WT:3 RSVD:2 (M):5 (N):4 P:16", "ALHR", "Aloha: Registration (RQR) or emergency requests (RQE) invited" }, + { 0, MPT_DOWN, MPT_ALHX, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=00 FUNC:3=101 CHAN4:4 WT:3 RSVD:2 (M):5 (N):4 P:16", "ALHX", "Aloha: Messages invited, except RQR" }, + { 0, MPT_DOWN, MPT_ALHF, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=00 FUNC:3=110 CHAN4:4 WT:3 RSVD:2 (M):5 (N):4 P:16", "ALHF", "Aloha: Fall-back mode" }, + /* Category '000' Messages: Acknowledgement Messages (Type '01') */ + { 0, MPT_BOTH, MPT_ACK, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=01 FUNC:3=000 IDENT2:13 QUAL:1 (N):4 P:16", "ACK", "Ack: General acknowledgement" }, + { 0, MPT_BOTH, MPT_ACKI, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=01 FUNC:3=001 IDENT2:13 QUAL:1 (N):4 P:16", "ACKI", "Ack: Intermediate acknowledgement, more signalling to follow" }, + { 0, MPT_BOTH, MPT_ACKQ, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=01 FUNC:3=010 IDENT2:13 QUAL:1 (N):4 P:16", "ACKQ", "Ack: Acknowledge, call queued" }, + { 0, MPT_BOTH, MPT_ACKX, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=01 FUNC:3=011 IDENT2:13 QUAL:1 (N):4 P:16", "ACKX", "Ack: Acknowledge, message rejected" }, + { 0, MPT_BOTH, MPT_ACKV, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=01 FUNC:3=100 IDENT2:13 QUAL:1 (N):4 P:16", "ACKV", "Ack: Acknowledge, called unit unavailable" }, + { 0, MPT_BOTH, MPT_ACKE, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=01 FUNC:3=101 IDENT2:13 QUAL:1 (N):4 P:16", "ACKE", "Ack: Acknowledge emergency call" }, + { 0, MPT_BOTH, MPT_ACKT, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=01 FUNC:3=110 IDENT2:13 QUAL:1 (N):4 P:16", "ACKT", "Ack: Acknowledge, try on given address" }, + { 0, MPT_BOTH, MPT_ACKB, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=01 FUNC:3=111 IDENT2:13 QUAL:1 (N):4 P:16", "ACKB", "Ack: Acknowledge, call-back, or negative acknowledgement" }, + /* Category '000' Messages: Request Messages (Type '10') */ + { 0, MPT_UP, MPT_RQS, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=000 IDENT2:13 DT:1 LEVEL:1 EXT:1 FLAG1:1 FLAG2:1 P:16", "RQS", "Request: Request Simple call" }, + { 0, MPT_UP, MPT_RQSpare, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=001 PARAMETERS:18 P:16", "RQSpstr", "Request: Spare. Available for customisation" }, + { 0, MPT_UP, MPT_RQX, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=010 IDENT2:13 RSVD:5 P:16", "RQX", "Request: Request call cancel / abort transaction" }, + { 0, MPT_UP, MPT_RQT, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=011 IDENT2:13 SD:2 DIV:1 FLAG1:1 FLAG2:1 P:16", "RQT", "Request: Request call diversion" }, + { 0, MPT_UP, MPT_RQE, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=100 IDENT2:13 D:1 RSVD:1 EXT:1 FLAG1:1 FLAG2:1 P:16", "RQE", "Request: Request emergency call" }, + { 0, MPT_UP, MPT_RQR, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=101 INFO:15 RSVD:3 P:16", "RQR", "Request: Request to register" }, + { 0, MPT_UP, MPT_RQQ, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=110 IDENT2:13 STATUS:5 P:16", "RQQ", "Request: Request status transaction" }, + { 0, MPT_UP, MPT_RQC, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=111 IDENT2:13 SLOTS:2 EXT:1 FLAG1:1 FLAG2:1 P:16", "RQC", "Request: Request to send short data message" }, + /* Category '000' Messages: Ahoy Messages (Type '10') */ + { 0, MPT_DOWN, MPT_AHY, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=000 IDENT2:13 D:1 POINT:1 CHECK:1 E:1 AD:1 P:16", "AHY", "Ahoy: General availability check" }, + { 0, MPT_DOWN, MPT_AHYSpare, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=001 PARAMETERS:18 P:16", "AHYSpare", "Ahoy: Spare for customisation" }, + { 0, MPT_DOWN, MPT_AHYX, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=010 IDENT2:13 POINT:5 P:16", "AHYX", "Ahoy: Cancel alert/waiting state" }, + { 0, MPT_DOWN, MPT_AHYP, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=101 IDENT2:13 RSVD:5 P:16", "AHYP", "Ahoy: Called Unit Presence Monitoring" }, + { 0, MPT_DOWN, MPT_AHYQ, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=110 IDENT2:13 STATUS:5 P:16", "AHYQ", "Ahoy: Status message" }, + { 0, MPT_DOWN, MPT_AHYC, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=10 FUNC:3=111 IDENT2:13 SLOTS:2 DESC:3 P:16", "AHYC", "Ahoy: Short data invitation" }, + /* Category '000' Messages: Miscellaneous Control Messages (Type '11') */ + { 0, MPT_DOWN, MPT_MARK, "1 CHAN4:4 A:1 SYS:15 1 CAT:3=000 TYPE:2=11 FUNC:3=000 B:18 P:16", "MARK", "Misc: Control channel marker" }, + { 0, MPT_BOTH, MPT_MAINT, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=11 FUNC:3=001 CHAN:10 OPER:3 RSVD:5 P:16", "MAINT", "Misc: Call maintenance message" }, + { 0, MPT_DOWN, MPT_CLEAR, "1 CHAN:10 CONT:10 1 CAT:3=000 TYPE:2=11 FUNC:3=010 RSVD:4 SPARE:2 REVS:12=101010101010 P:16", "CLEAR", "Misc: Clear down from allocated channel" }, + { 0, MPT_DOWN, MPT_MOVE, "1 PFIX:7 IDENT1:13 1 CAT:3=000 TYPE:2=11 FUNC:3=011 CONT:10 (M):5 RSVD:2 SPARE:1 P:16", "MOVE", "Misc: Move to specified control channel" }, + { 0, MPT_DOWN, MPT_BCAST0, "1 SYSDEF:5=00000 SYS:15 1 CAT:3=000 TYPE:2=11 FUNC:3=100 CHAN:10 SPARE:2 RSVD:6 P:16", "BCAST", "Misc: Broadcast message: Announce control channel" }, + { 0, MPT_DOWN, MPT_BCAST1, "1 SYSDEF:5=00001 SYS:15 1 CAT:3=000 TYPE:2=11 FUNC:3=100 CHAN:10 SPARE:2 RSVD:6 P:16", "BCAST", "Misc: Broadcast message: Withdraw control channel" }, + { 0, MPT_DOWN, MPT_BCAST2, "1 SYSDEF:5=00010 SYS:15 1 CAT:3=000 TYPE:2=11 FUNC:3=100 PER:1 IVAL:5 PON:1 ID:1 RSVD:2 SPARE:8 P:16", "BCAST", "Misc: Broadcast message: Specify call maintenance parameter" }, + { 0, MPT_DOWN, MPT_BCAST3, "1 SYSDEF:5=00011 SYS:15 1 CAT:3=000 TYPE:2=11 FUNC:3=100 RSVD:4 SPARE:14 P:16", "BCAST", "Misc: Broadcast message: Specify registration parameters" }, + { 0, MPT_DOWN, MPT_BCAST4, "1 SYSDEF:5=00100 SYS:15 1 CAT:3=000 TYPE:2=11 FUNC:3=100 CHAN:10 SPARE:2 RSVD:2 ADJSITE:4 P:16", "BCAST", "Misc: Broadcast message: Broadcast adjected site control channel number" }, + { 0, MPT_DOWN, MPT_BCAST5, "1 SYSDEF:5=00101 SYS:15 1 CAT:3=000 TYPE:2=11 FUNC:3=100 CHAN:10 SPARE:2 RSVD:2 ADJSITE:4 P:16", "BCAST", "Misc: Broadcast message: Vote now advice" }, + /* Category '001' Messages */ + { 0, MPT_DOWN, MPT_SAMO, "1 PFIX:7 IDENT1:13 1 CAT:3=001 TYPE:1=0 PARAMETERS:22 P:16", "SAMO", "Sam: Outbound Single Address Message" }, + { 0, MPT_UP, MPT_SAMIU, "1 PFIX:7 IDENT1:13 1 CAT:3=001 TYPE:1=0 SOL:1=1 PARAMETERS:21 P:16", "SAMIU", "Sam: Inbound Unsolicited Single Address Message" }, + { 0, MPT_UP, MPT_SAMIS, "1 PARAMETERS1:20 1 CAT:3=001 TYPE:1=0 SOL:1=0 DESC:3 PARAMETERS2:18 P:16", "SAMIS", "Sam: Inbound Solicited Single Address Message" }, + { 0, MPT_BOTH, MPT_HEAD, "1 PFIX:7 IDENT1:13 1 CAT:3=001 TYPE:1=1 LEN:2 PREFIX2:7 IDENT2:13 P:16", "HEAD", "Short Data Message Header" }, + /*0Category '010' Messages */ + { 0, MPT_UP, MPT_RQD, "1 PFIX:7 IDENT1:13 1 CAT:3=010 KIND:1=1 PORT:3 FAD:1 IDENT2:13 INTER:1 LEVEL:1 HADT:1 E:1 MODEM:1 P:16", "RQD", "Request Standard Data Communication" }, + { 0, MPT_DOWN, MPT_AHYD, "1 PFIX:7 IDENT1:13 1 CAT:3=010 KIND:1=1 PORT:3 RSVD:1 IDENT2:13 INTER:1 POINT:1 HADT:1 E:1 AD:1 P:16", "AHYD", "Availability Check for Standard Data" }, + { 0, MPT_DOWN, MPT_GTT, "1 PFIX:7 IDENT1:13 1 CAT:3=010 KIND:1=0 CHAN:10 O/R:1 RATE:1 TRANS:10 P:16", "GTT", "Go To Transaction" }, + { 0, MPT_UP, MPT_DRUGI, "1 PFIX:7 IDENT1:13 1 CAT:3=010 KIND:1=0 RNITEL:6 TNITEL:6 TRANS:10 P:16", "DRUGI", "Standard Data Random access, Radio Unit General Information" }, + /* Category '101' Messages */ + { 0, MPT_BOTH, MPT_DACKD, "1 PFIX:7 IDENT1:13 1 CAT:3=101 KIND:1=0 JOB:4=0101 RSVD:5 REASON:3 TRANS:10 P:16", "DACKD", "Standard Data general purpose acknowlegement" }, + { 0, MPT_DOWN, MPT_DACK_DAL, "1 ATRANS:10 RTRANS:10 1 CAT:3=101 KIND:1=0 JOB:4=0000 W/F:3 P/N:1 RSVD:2 DN:5 TNITEL:6 ITENUM:1 P:16", "DACK+DAL", "Standard Data Codeword + DAL" }, + { 0, MPT_DOWN, MPT_DACK_DALG, "1 ATRANS:10 RTRANS:10 1 CAT:3=101 KIND:1=0 JOB:4=0001 W/F:3 P/N:1 RSVD:2 DN:5 TNITEL:6 ITENUM:1 P:16", "DACK+DALG", "Standard Data Codeword + DALG" }, + { 0, MPT_DOWN, MPT_DACK_DALN, "1 ATRANS:10 RTRANS:10 1 CAT:3=101 KIND:1=0 JOB:4=0010 W/F:3 P/N:1 RSVD:2 DN:5 TNITEL:6 ITENUM:1 P:16", "DACK+DALN", "Standard Data Codeword + DALN" }, + { 0, MPT_BOTH, MPT_DACK_GO, "1 ATRANS:10 RTRANS:10 1 CAT:3=101 KIND:1=0 JOB:4=0011 RSVD:3 P/N:1 RSVD:1 RNITEL:6 TNITEL:6 ITENUM:1 P:16", "DACK+'GO'", "Standard Data Codeword + 'GO'" }, + { 0, MPT_BOTH, MPT_DACKZ, "1 ATRANS:10 SPRE:10 1 CAT:3=101 KIND:1=0 JOB:4=0100 SX:3 SPRE:7 CAUSE:8 P:16", "DACKZ", "Standard Data Acknowledgement for expedited data" }, + { 0, MPT_DOWN, MPT_DAHY, "1 TRANS:10 RSVD:10 1 CAT:3=101 KIND:1=0 JOB:4=1000 RSVD:10 SPARE:8 P:16", "DAHY", "Standard Data General ahoy" }, + { 0, MPT_DOWN, MPT_DAHYZ, "1 SPRE:10 RSVD:10 1 CAT:3=101 KIND:1=0 JOB:4=1100 SX:3 SPRE:7 CAUSE:8 P:16", "DAHYZ", "Standard Data ahoy containing expedited data" }, + { 0, MPT_DOWN, MPT_DAHYX, "1 PFIX:7 IDENT1:13 1 CAT:3=101 KIND:1=0 JOB:4=1110 I/T:1 RESP:1 SPRE:3 TOC:3 TRANS:10 P:16", "DHAYX", "Standard Data ahoy containing expedited data" }, + { 0, MPT_BOTH, MPT_RLA, "1 TRANS:10 RSVD:10 1 CAT:3=101 KIND:1=0 JOB:4=1111 RSVD:12 SPARE:6 P:16", "RLA", "Repeat last ACK" }, + { 0, MPT_UP, MPT_DRQG, "1 TRANS:10 SPARE:7 RSVD:3 1 CAT:3=101 KIND:1=0 JOB:4=1010 RSVD:18 P:16", "DRQG", "Repeat group message" }, + { 0, MPT_UP, MPT_DRQZ, "1 TRANS:10 SPRE:10 1 CAT:3=101 KIND:1=0 JOB:4=1100 SX:3 SPRE:7 CAUSE:8 P:16", "DRQZ", "Request containing expedited data" }, + { 0, MPT_UP, MPT_DRQX, "1 PFIX:7 IDENT1:13 1 CAT:3=101 KIND:1=0 JOB:4=1110 SPRE:5 TOC:3 TRANS:10 P:16", "DRQX", "Request to close a transaction" }, + { 0, MPT_BOTH, MPT_SACK, "1 ATRANS:10 EFLAGS:10 1 CAT:3=101 KIND:1=1 TASK:1=0 RSVD:2 EFLAGS:13 ONES:4 AD:1 ITENUM:1 P:16", "SACK", "Standard Data Selective Acknowledgement Header" }, + { 0, MPT_BOTH, MPT_SITH_I, "1 TRANS:10 USERDATA:10 1 CAT:3=101 KIND:1=1 TASK:1=1 I/G:1=0 MORE:1 LASTBIT:6 FRAGL:6 TNITEL:6 ITENUM:1 P:16", "SITH", "Standard Data Address Codeword (Individual) Dataitem" }, + { 0, MPT_DOWN, MPT_SITH_G, "1 TRANS:10 USERDATA:10 1 CAT:3=101 KIND:1=1 TASK:1=1 I/G:1=1 MORE:1 LASTBIT:6 FRAGL:8 RSVD:4 ITENUM:1 P:16", "SITH", "Standard Data Address Codeword (Group) Dataitem" }, + /* Startup & CCSC */ + { 1, MPT_DOWN, MPT_START_SYNC, "LET:32=00000000000000000000000000000000 PREAMBLE:16=1010101010101010 1100010011010111", "Startup", "Startup sequence on CC" }, + { 0, MPT_DOWN, MPT_CCSC, "0 SYS:15 CCS:16 PREAMBLE:16=1010101010101010 P:16", "CCSC/DCSC", "System Identification" }, + { 1, MPT_DOWN, MPT_START_SYNT, "LET:32=00000000000000000000000000000000 PREAMBLE:16=1010101010101010 0011101100101000", "SYNT", "Startup sequence on TC" }, + + /* Data codewords following ACKT(QUAL=0) address codeword */ + { 1, MPT_DOWN, MPT_ACKT_DT1, "0 RSA:1 FCW:2 BCD11:44 P:16", "ACKT Data 1", "Ack: Acknowledge, try on given address; Data Word 1" }, + { 1, MPT_DOWN, MPT_ACKT_DT2, "0 RSVD:10 SP:1=0 PARAMETERS:36 P:16", "ACKT Data 2", "Ack: Acknowledge, try on given address; Data Word 2" }, + { 1, MPT_DOWN, MPT_ACKT_DT3, "0 RSVD:10 SP:1=1 RSVD:21 EXCHANGE:2 Number:13 P:16", "ACKT Data 3", "Ack: Acknowledge, try on given address; Data Word 3" }, + { 1, MPT_DOWN, MPT_ACKT_DT4, "0 RSVD:26 GF:1 PFIXT:7 IDENTT:13 P:16", "ACKT Data 4", "Ack: Acknowledge, try on given address; Data Word 4" }, + /* Data codeword following AHY address codeword */ + { 1, MPT_DOWN, MPT_AHY_DT, "0 FORM:3=000 RSVD:24 PFIX2:7 IDENT2:13 P:16", "AHY Data", "Ahoy: General availability check; Data Word" }, + /* Data codeword following AHYQ address codeword */ + { 1, MPT_DOWN, MPT_AHYQ_DT, "0 RSVD:27 PFIX:7 IDENT2:13 P:16", "AHYQ Data", "Ahoy: Status message; Data Word" }, + /* Data codewords appended to SAMIS, Mode 1 */ + { 1, MPT_UP, MPT_SAMIS_DT, "0 RSVD:3 BCD11:44 P:16", "SAMIS Data", "Sam: Inbound Solicited Single Address Message; Data Word" }, + /* Data codeword(s) following HEAD address codeword */ + { 1, MPT_DOWN, MPT_HEAD_DT, "0 RSA:1 PARAMETERS:46 P:16", "HEAD Data", "Short Data Message Header; Data Word" }, + /* Data codeword following AHYD address codeword */ + { 1, MPT_DOWN, MPT_AHYD_DT, "0 FORM:3=000 RSVD:24 PFIX2:7 IDENT2:13 P:16", "AHYD Data", "Availability Check for Standard Data; Data Word" }, + /* Data codeword following Standard Data Acknowledgement Header SACK */ + { 1, MPT_DOWN, MPT_SACK_DT, "0 ONES:4 EFLAGS:40 RSVD:3 P:16", "SACK Data", "Standard Data Selective Acknowledgement; Data Word" }, +}; + +static struct mpt1327_defintion { + int specific_only; + enum mpt1327_codeword_dir dir; + enum mpt1327_codeword_type type; + const char *short_name; + const char *long_name; + uint64_t bits, mask; + enum mpt1327_parameters params[64]; +} mpt1327_definitions[_NUM_MPT_DEFINITIONS]; + +static void _CHECK_MAX_BITS(int bits, const char *name) +{ + if (bits == 64) { + fprintf(stderr, "Message '%s' exceeds 64 bits, please fix!\n", name); + abort(); + } +} + +void init_codeword(void) +{ + uint64_t bits, mask; + int num_bits; + enum mpt1327_parameters params[64]; + char *param_text, *next_param, *param, *param_bits, *param_const; + int i, j, p, b; + + if (sizeof(definitions) / sizeof(definitions[0]) != _NUM_MPT_DEFINITIONS) { + fprintf(stderr, "definitions[] has different size than enum mpt1327_codeword_type, please fix!\n"); + abort(); + } + if (sizeof(mpt1327_parameter_names) / sizeof(mpt1327_parameter_names[0]) != _NUM_MPT_PARAMETERS) { + fprintf(stderr, "mpt1327_parameter_names[] has different size than enum mpt1352_parameters, please fix!\n"); + abort(); + } + + /* parse all message definitions */ + for (i = 0; i < _NUM_MPT_DEFINITIONS; i++) { + bits = mask = 0; + num_bits = 0; + param_text = next_param = strdup(definitions[i].def); + while ((param = strsep(&next_param, " "))) { + if (param[0] >= '0' && param[0] <= '9') { + /* param is a constant */ + while (*param) { + if (*param < '0' || *param > '1') { + fprintf(stderr, "Constant '%s' does not consists of '0' or '1' only, please fix!\n", param); + abort(); + } + _CHECK_MAX_BITS(num_bits, definitions[i].short_name); + if ((*param++ & 1)) + bits |= 0x8000000000000000 >> num_bits; + mask |= 0x8000000000000000 >> num_bits; + params[num_bits] = 0; + num_bits++; + } + } else { + /* param is a parameter */ + param_bits = strchr(param, ':'); + if (!param_bits) { + fprintf(stderr, "Param '%s' does not have a ':' to define number of bits, please fix!\n", param); + abort(); + } + *param_bits++ = '\0'; + /* get parameter from param text */ + for (p = 0; p < _NUM_MPT_PARAMETERS; p++) { + if (!strcmp(mpt1327_parameter_names[p].name, param)) + break; + } + if (p == _NUM_MPT_PARAMETERS) { + fprintf(stderr, "Param '%s' is not found in list of parameter names, please fix!\n", param); + p = 0; + } + if (p > _NUM_MPT_PARAMETERS) { + fprintf(stderr, "There are more parameters than definitons, please fix!\n"); + abort(); + } + /* get constant for parameter, if given */ + param_const = strchr(param_bits, '='); + if (param_const) { + *param_const++ = '\0'; + if ((int)strlen(param_const) != atoi(param_bits)) { + fprintf(stderr, "Param '%s' has %s bits, but constant '%s' does not, please fix!\n", param, param_bits, param_const); + abort(); + } + } + for (b = 0; b < atoi(param_bits); b++) { + _CHECK_MAX_BITS(num_bits, definitions[i].short_name); + if (param_const) { + if (param_const[b] < '0' || param_const[b] > '1') { + fprintf(stderr, "Param '%s' has a constant '%s', but must only consist of '0' or '1', please fix!\n", param, param_const); + abort(); + } + if (param_const[b] == '1') + bits |= 0x8000000000000000 >> num_bits; + mask |= 0x8000000000000000 >> num_bits; + } + params[num_bits] = p; + num_bits++; + } + } + } + free(param_text); + if (num_bits != 64) { + fprintf(stderr, "Message '%s' (has %d bits) is not exactly 64 bits, please fix!\n", definitions[i].short_name, num_bits); + abort(); + } +#if 0 + printf("Message definition for '%s'\n", definitions[i].short_name); + printf("%s\n", definitions[i].def); + for (b = 0; b < 64; b++) + printf("mask=%d data=%d name=%s\n", (mask >> (63 - b)) & 1, (bits >> (63 - b)) & 1, mpt1327_parameter_names[params[b]].name); +#endif + /* check type */ + if ((int)definitions[i].type != i) { + fprintf(stderr, "Message '%s' has type %d, but index is %d. Type and index must match, please fix!\n", definitions[i].short_name, definitions[i].type, i); + abort(); + } + /* store codeword definition */ + mpt1327_definitions[i].specific_only = definitions[i].specific_only; + mpt1327_definitions[i].dir = definitions[i].dir; + mpt1327_definitions[i].type = definitions[i].type; + mpt1327_definitions[i].short_name = definitions[i].short_name; + mpt1327_definitions[i].long_name = definitions[i].long_name; + mpt1327_definitions[i].bits = bits; + mpt1327_definitions[i].mask = mask; + memcpy(mpt1327_definitions[i].params, params, sizeof(params)); + /* check for duplicate message types */ + for (j = 0; j < i; j++) + if (mpt1327_definitions[j].type == definitions[i].type) + break; + if (j < i) { + fprintf(stderr, "Message '%s' is duplicated (index %d and %d have same message type), please fix!\n", definitions[i].short_name, j, i); + abort(); + } + } +} + +/* calculate check bits, ispired by olle@toolcrypt.org (snable) */ +uint16_t mpt1327_checkbits(uint64_t bits, uint16_t *parityp) +{ + uint16_t check = 0x0000, parity = 0; + int bit; + int b; + + /* calculate check at upper 15 bits */ + for (b = 0; b < 48; b++) { + bit = (bits >> (63 - b)) & 1; + parity ^= bit; + if (bit != (check >> 15)) + check ^= 0x6815; + check <<= 1; + } + + /* invert lowest check bit (of 15 upper bits) */ + check ^= 0x0002; + + /* finish parity and append as lest bit (bit 0) */ + for (b = 1; b < 16; b++) + parity ^= (check >> b) & 1; + check ^= parity; + + if (parityp) + *parityp = parity; + return check; +} + +static void debug_codeword(const char *prefix, int i, uint64_t bits, int enc) +{ + uint64_t value; + char text[1024]; + int column; + int b; + + if (debuglevel > DEBUG_INFO) + return; + + switch (mpt1327_definitions[i].type) { + case MPT_START_SYNC: + case MPT_CCSC: + case MPT_START_SYNT: + case MPT_ALH: + case MPT_ALHS: + case MPT_ALHD: + case MPT_ALHE: + case MPT_ALHR: + case MPT_ALHX: + case MPT_ALHF: + case MPT_BCAST0: + case MPT_BCAST1: + case MPT_BCAST2: + case MPT_BCAST3: + case MPT_BCAST4: + case MPT_BCAST5: + if (enc && debuglevel > DEBUG_DEBUG) + return; + default: + ; + } + + PDEBUG(DFRAME, DEBUG_INFO, "%s Codeword %s: %s\n", prefix, mpt1327_definitions[i].short_name, mpt1327_definitions[i].long_name); + column = 0; + for (b = 0; b < 64; b++) { + /* if we have first parameter or we swith to next parameter */ + if (b == 0 || mpt1327_definitions[i].params[b] != mpt1327_definitions[i].params[b - 1]) { + value = 0; + if (b != 0) + text[column++] = ' '; + if (mpt1327_definitions[i].params[b]) { + strcpy(text + column, mpt1327_parameter_names[mpt1327_definitions[i].params[b]].name); + column += strlen(mpt1327_parameter_names[mpt1327_definitions[i].params[b]].name); + text[column++] = '='; + } + } + value = (value << 1) | ((bits >> (63 - b)) & 1); + text[column++] = ((bits >> (63 - b)) & 1) + '0'; +#if 0 + if (b == 63 || mpt1327_definitions[i].params[b] != mpt1327_definitions[i].params[b + 1]) { + sprintf(text + column, "(%" PRIu64 ")", value); + column += strlen(text + column); + } +#endif + } + text[column] = '\0'; + PDEBUG(DFRAME, DEBUG_INFO, "%s\n", text); +} + +uint64_t mpt1327_encode_codeword(mpt1327_codeword_t *codeword) +{ + uint64_t params[_NUM_MPT_PARAMETERS]; + uint64_t bits; + int i, b; + + /* check all codeword definitions */ + for (i = 0; i < _NUM_MPT_DEFINITIONS; i++) { + if (mpt1327_definitions[i].type == codeword->type) + break; + } + if (i == _NUM_MPT_DEFINITIONS) { + fprintf(stderr, "Codeword not found for type %d, please fix!\n", codeword->type); + abort(); + } + + /* fill parameters */ + memcpy(params, codeword->params, sizeof(params)); + bits = 0; + for (b = 63; b >= 0; b--) { + if ((params[mpt1327_definitions[i].params[b]] & 1)) + bits |= (0x8000000000000000 >> b); + params[mpt1327_definitions[i].params[b]] >>= 1; + } + + /* set constants */ + bits = (bits & ~mpt1327_definitions[i].mask) | mpt1327_definitions[i].bits; + + /* calculate MPT_CCS (See MTP1327 Appendix 3) */ + if (codeword->type == MPT_CCSC) { + uint64_t ccs = 0xaaaac4d400000000 | ((codeword->params[MPT_SYS] & 0x7fff) << 17); + uint16_t parity; +assumption_wrong: + ccs = (ccs & 0xffffffffffff0000) | mpt1327_checkbits(ccs, &parity); + if (parity == 0) { + ccs |= 0x10000; + goto assumption_wrong; + } + bits = (bits & 0xffff0000ffffffff) | (((ccs ^ 0x2) & 0x1fffe) << 31); + } + + /* add parity, if not forced by definition */ + if (!(mpt1327_definitions[i].mask & 0xffff)) + bits = (bits & 0xffffffffffff0000) | mpt1327_checkbits(bits, NULL); + + debug_codeword("Transmitting", i, bits, 1); + + return bits; +} + +int mpt1327_decode_codeword(mpt1327_codeword_t *codeword, int specific, enum mpt1327_codeword_dir dir, uint64_t bits) +{ + int i, b; + + memset(codeword, 0, sizeof(*codeword)); + codeword->dir = dir; + + /* check all codeword definitions */ + for (i = 0; i < _NUM_MPT_DEFINITIONS; i++) { + /* skip if direction does not match */ + if (dir != mpt1327_definitions[i].dir && mpt1327_definitions[i].dir != MPT_BOTH) + continue; + if (specific >= 0) { + /* select where type matches */ + if (mpt1327_definitions[i].type == (unsigned int)specific) + break; + } else { + /* ignore message definitions that require specifiying codeword type */ + if (mpt1327_definitions[i].specific_only) + continue; + /* select where masked bits match */ + if (mpt1327_definitions[i].bits == (bits & mpt1327_definitions[i].mask)) + break; + } + } + if (i == _NUM_MPT_DEFINITIONS) { + char debug[256]; + PDEBUG(DFRAME, DEBUG_NOTICE, "Received unknown codeword or loopback from transmitter side.\n"); + for (b = 0; b < 64; b++) + debug[b] = ((bits >> (63 - b)) & 1) + '0'; + debug[b] = '\0'; + PDEBUG(DFRAME, DEBUG_DEBUG, "%s\n", debug); + return -EINVAL; + } + codeword->type = mpt1327_definitions[i].type; + codeword->short_name = mpt1327_definitions[i].short_name; + codeword->long_name = mpt1327_definitions[i].long_name; + + /* fill parameters */ + for (b = 0; b < 64; b++) { + codeword->params[mpt1327_definitions[i].params[b]] <<= 1; + if ((bits & (0x8000000000000000 >> b))) + codeword->params[mpt1327_definitions[i].params[b]] |= 1; + } + + debug_codeword("Receiving", i, bits, 0); + + return 0; +} + diff --git a/src/mpt1327/message.h b/src/mpt1327/message.h new file mode 100644 index 0000000..0cd9d19 --- /dev/null +++ b/src/mpt1327/message.h @@ -0,0 +1,227 @@ + +#define IDENT_ALLI 8191 /* System-wide ident */ +#define IDENT_TSCI 8190 /* Ident of TSC */ +#define IDENT_IPFIXI 8189 /* Interprefix ident */ +#define IDENT_SDMI 8188 /* Short data message ident */ +#define IDENT_DIVERTI 8187 /* Divert ident */ +#define IDENT_INCI 8186 /* Include ident */ +#define IDENT_REGI 8185 /* Registration ident */ +#define IDENT_PSTNSI1 8121 /* Short-form PSTN idents */ +#define IDENT_NETSI1 8121 /* Short-form data Network idents */ +#define IDENT_DNI 8103 /* Data Network gateway ident */ +#define IDENT_PABXI 8102 /* PABX gateway ident */ +#define IDENT_PSTNGI 8101 /* General PSTN gateway ident */ +#define IDENT_DUMMYI 0 /* Dummy ident */ + +#define OPER_PRESSEL_ON 0 +#define OPER_PRESSEL_OFF 1 +#define OPER_PERIODIC 2 +#define OPER_DISCONNECT 3 +#define OPER_SPARE 4 +#define OPER_RESERVED 5 +#define OPER_CLEAR 6 +#define OPER_DISABLE 7 + +enum mpt1327_codeword_dir { + MPT_DOWN, + MPT_UP, + MPT_BOTH, +}; + +enum mpt1327_codeword_type { + MPT_FILLER = 0, + MPT_GTC, + MPT_ALH, + MPT_ALHS, + MPT_ALHD, + MPT_ALHE, + MPT_ALHR, + MPT_ALHX, + MPT_ALHF, + MPT_ACK, + MPT_ACKI, + MPT_ACKQ, + MPT_ACKX, + MPT_ACKV, + MPT_ACKE, + MPT_ACKT, + MPT_ACKB, + MPT_RQS, + MPT_RQSpare, + MPT_RQX, + MPT_RQT, + MPT_RQE, + MPT_RQR, + MPT_RQQ, + MPT_RQC, + MPT_AHY, + MPT_AHYSpare, + MPT_AHYX, + MPT_AHYP, + MPT_AHYQ, + MPT_AHYC, + MPT_MARK, + MPT_MAINT, + MPT_CLEAR, + MPT_MOVE, + MPT_BCAST0, + MPT_BCAST1, + MPT_BCAST2, + MPT_BCAST3, + MPT_BCAST4, + MPT_BCAST5, + MPT_SAMO, + MPT_SAMIU, + MPT_SAMIS, + MPT_HEAD, + MPT_RQD, + MPT_AHYD, + MPT_GTT, + MPT_DRUGI, + MPT_DACKD, + MPT_DACK_DAL, + MPT_DACK_DALG, + MPT_DACK_DALN, + MPT_DACK_GO, + MPT_DACKZ, + MPT_DAHY, + MPT_DAHYZ, + MPT_DAHYX, + MPT_RLA, + MPT_DRQG, + MPT_DRQZ, + MPT_DRQX, + MPT_SACK, + MPT_SITH_I, + MPT_SITH_G, + MPT_START_SYNC, + MPT_CCSC, + MPT_START_SYNT, + MPT_ACKT_DT1, + MPT_ACKT_DT2, + MPT_ACKT_DT3, + MPT_ACKT_DT4, + MPT_AHY_DT, + MPT_AHYQ_DT, + MPT_SAMIS_DT, + MPT_HEAD_DT, + MPT_AHYD_DT, + MPT_SACK_DT, + _NUM_MPT_DEFINITIONS +}; + +enum mpt1327_parameters { + MPT_CONSTANT = 0, + MPT_PFIX, + MPT_IDENT1, + MPT_D, + MPT_CHAN, + MPT_IDENT2, + MPT_N, + MPT_P, + MPT_CAT, + MPT_TYPE, + MPT_FUNC, + MPT_CHAN4, + MPT_WT, + MPT_RSVD, + MPT_M, + MPT_QUAL, + MPT_DT, + MPT_LEVEL, + MPT_EXT, + MPT_FLAG1, + MPT_FLAG2, + MPT_PARAMETERS, + MPT_SD, + MPT_DIV, + MPT_INFO, + MPT_STATUS, + MPT_SLOTS, + MPT_POINT, + MPT_CHECK, + MPT_E, + MPT_AD, + MPT_DESC, + MPT_A, + MPT_B, + MPT_SPARE, + MPT_REVS, + MPT_OPER, + MPT_SYS, + MPT_CONT, + MPT_SYSDEF, + MPT_PER, + MPT_IVAL, + MPT_PON, + MPT_ID, + MPT_ADJSITE, + MPT_SOL, + MPT_LEN, + MPT_PREFIX2, + MPT_KIND, + MPT_PORT, + MPT_FAD, + MPT_INTER, + MPT_HADT, + MPT_MODEM, + MPT_O_R, + MPT_RATE, + MPT_TRANS, + MPT_RNITEL, + MPT_TNITEL, + MPT_JOB, + MPT_REASON, + MPT_ATRANS, + MPT_EFLAGS, + MPT_TASK, + MPT_ONES, + MPT_ITENUM, + MPT_USERDATA, + MPT_I_G, + MPT_MORE, + MPT_LASTBIT, + MPT_FRAGL, + MPT_RTRANS, + MPT_W_F, + MPT_P_N, + MPT_DN, + MPT_SPRE, + MPT_SX, + MPT_CAUSE, + MPT_I_T, + MPT_RESP, + MPT_TOC, + MPT_CCS, + MPT_LET, + MPT_PREAMBLE, + MPT_PARAMETERS1, + MPT_PARAMETERS2, + MPT_BCD11, + MPT_RSA, + MPT_FCW, + MPT_SP, + MPT_EXCHANGE, + MPT_Number, + MPT_GF, + MPT_PFIXT, + MPT_IDENTT, + MPT_FORM, + MPT_PFIX2, + _NUM_MPT_PARAMETERS +}; + +extern char *mpt1327_bcd; + +typedef struct mpt1327_codeword { + enum mpt1327_codeword_dir dir; + enum mpt1327_codeword_type type; + const char *short_name, *long_name; + uint64_t params[_NUM_MPT_PARAMETERS]; +} mpt1327_codeword_t; + +void init_codeword(void); +uint16_t mpt1327_checkbits(uint64_t bits, uint16_t *parityp); +uint64_t mpt1327_encode_codeword(mpt1327_codeword_t *codeword); +int mpt1327_decode_codeword(mpt1327_codeword_t *codeword, int specific, enum mpt1327_codeword_dir dir, uint64_t bits); + diff --git a/src/mpt1327/mpt1327.c b/src/mpt1327/mpt1327.c new file mode 100755 index 0000000..8d44fee --- /dev/null +++ b/src/mpt1327/mpt1327.c @@ -0,0 +1,1661 @@ +/* protocol handling + * + * (C) 2021 by Andreas Eversberg + * All Rights Reserved + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * How code-word scheduling works on downlink (tx_sched): + * + * The DSP mode can be set to CONTROL or TRAFFIC, depending on the mode the + * channel is working. Depending on that, a function is called for every + * code-word to be transmitted, one to schedule code-words on control channel, + * one to schedule code-words on traffic channel. + * + * The scheduler uses a state that indicates what was last scheduled, i.e. what + * is currently transmitted. If nothing is scheduled yet, an IDLE state is set. + * When switching between CONTROL and TRAFFIC mode, the different states (for + * each mode) are handled as they would be IDLE state, so that no reset to IDLE + * state is required when changing DSP mode. + * + * An IDLE state results in a startup sequence on control channel (SYNC) or on + * traffic channel (SYNT), whenever a message must be scheduled. The message to + * be scheduled depends on the unit states. All units are queried for any + * message to be scheduled. If no message on control channel need to be + * scheduled, an ALH message is scheduled, so that random access is possible. + * On control channel the address conde-words alternate with CCSC code-word. + * + * To prevent random access when a unit is requested to transmit more than two + * code-word, a dummy frame counter is set. Then a dummy AHY message is + * scheduled, to prevent random access by other units in that slot. + */ + +/* + * How code-word scheduling works on uplink (rx_sched): + * + * The DSP mode can be set to CONTROL or TRAFFIC, depending on the mode the + * channel is working. Depending on that, a function is called for every + * code-word received, one for code-words on control channel, one for + * code-words on traffic channel. + * + * Most messages have an address code-word only, so the message type is defined + * by the elements in the code-word. Additional data code-words (that may + * follow an address code-word) do not have a message type, because they are + * defined by the previous address code-word. If a message has additional data + * code-words, a data word counter is set, so that subsequent data code-words + * are parsed as defined by the address code-word. In case of a CRC error, the + * message resets into un-synced state, i.e. waiting for next sync + address + * code-word. + */ + +#define CHAN mpt1327->sender.kanal + +#include +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "../libmobile/call.h" +#include "../libmobile/cause.h" +#include "../libosmocc/message.h" +#include "mpt1327.h" +#include "dsp.h" +#include "message.h" + +/* Timers and counters */ +#define RESPONSE_TIMEOUT 1.0 +#define REPEAT_GTC 1 +#define REPEAT_AHY 1 +#define REPEAT_AHYC 1 +#define REPEAT_AHYX 3 +#define REPEAT_CLEAR 3 + +/* Sysdef + * + */ + +static mpt1327_sysdef_t sysdef; + +void init_sysdef (uint16_t sys, int wt, int per, int pon, int timeout) +{ + memset(&sysdef, 0, sizeof(sysdef)); + + sysdef.sys = sys; + sysdef.wt = wt; + sysdef.per = per; + sysdef.pon = pon; + sysdef.timeout = timeout; + sysdef.framelength = 3; + sysdef.bcast_slots = 10; /* every seconds is good */ +} + +/* + * Units handling + */ + +static mpt1327_unit_t *unit_list = NULL; + +#define UNIT_IDLE 0 +#define UNIT_REGISTER_ACK (1 << 0) /* need to ack registration */ +#define UNIT_DIVERSION_REJ (1 << 1) /* need to nack diversion */ +#define UNIT_CALLING_REJ (1 << 2) /* need to reject call */ +#define UNIT_CALLING_AHYC (1 << 3) /* need to request SAMIS */ +#define UNIT_CALLING_SAMIS (1 << 4) /* wait for SAMIS response */ +#define UNIT_CALLED_AHY (1 << 5) /* need to request ACK */ +#define UNIT_CALLED_AHYX (1 << 6) /* cancel call */ +#define UNIT_CALLED_ACK (1 << 7) /* wait foro AHY response */ +#define UNIT_GTC_P (1 << 8) /* need to assign channel (same prefix) */ +#define UNIT_GTC_A (1 << 9) /* need to assign channel (calling unit) */ +#define UNIT_GTC_B (1 << 10) /* need to assign channel (called unit) */ +#define UNIT_CALL (1 << 11) /* established call */ +#define UNIT_CALL_CLEAR (1 << 12) /* established call */ +#define UNIT_CANCEL_ACK (1 << 13) /* need to ack cancelation */ + +const char *unit_state_name(uint64_t state) +{ + static char invalid[32]; + + switch (state) { + case UNIT_IDLE: + return "IDLE"; + case UNIT_REGISTER_ACK: + return "REGISTER-ACK"; + case UNIT_DIVERSION_REJ: + return "DIVERSION-REJ"; + case UNIT_CALLING_REJ: + return "CALLING-REJ"; + case UNIT_CALLING_AHYC: + return "CALLING-AHYC"; + case UNIT_CALLING_SAMIS: + return "CALLING-SAMIS"; + case UNIT_CALLED_AHY: + return "CALLED-AHY"; + case UNIT_CALLED_AHYX: + return "CALLED-AHYX"; + case UNIT_CALLED_ACK: + return "CALLED-ACK"; + case UNIT_GTC_P: + return "GTC-BOTH"; + case UNIT_GTC_A: + return "GTC-OTHER-UNIT"; + case UNIT_GTC_B: + return "GTC-UNIT"; + case UNIT_CALL: + return "CALL"; + case UNIT_CALL_CLEAR: + return "CALL-CLEAR"; + case UNIT_CANCEL_ACK: + return "CANCEL-ACK"; + } + + sprintf(invalid, "invalid(0x%" PRIx64 ")", state); + return invalid; +} + +void unit_new_state(mpt1327_unit_t *unit, uint64_t new_state) +{ + PDEBUG(DMPT1327, DEBUG_DEBUG, "Radio Unit (Prefix:%d Ident:%d) state: %s -> %s\n", unit->prefix, unit->ident, unit_state_name(unit->state), unit_state_name(new_state)); + unit->state = new_state; +} + +static void unit_timeout(struct timer *timer); + +mpt1327_unit_t *get_unit(uint8_t prefix, uint16_t ident) +{ + mpt1327_unit_t **unitp; + + for (unitp = &unit_list; *unitp; unitp = &((*unitp)->next)) { + if ((*unitp)->prefix == prefix + && (*unitp)->ident == ident) + break; + } + + if (!(*unitp)) { + PDEBUG(DDB, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) added to database\n", prefix, ident); + *unitp = calloc(1, sizeof(mpt1327_unit_t)); + timer_init(&(*unitp)->timer, unit_timeout, (*unitp)); + (*unitp)->state = UNIT_IDLE; + (*unitp)->prefix = prefix; + (*unitp)->ident = ident; + } + + return *unitp; +} + +mpt1327_unit_t *find_unit_state(uint32_t state, mpt1327_t *tc) +{ + mpt1327_unit_t *unit; + + for (unit = unit_list; unit; unit = unit->next) { + if (tc && unit->tc != tc) + continue; + if ((unit->state & state)) + break; + } + + return unit; +} + +mpt1327_unit_t *find_unit_callref(uint32_t callref) +{ + mpt1327_unit_t *unit; + + for (unit = unit_list; unit; unit = unit->next) { + if ((unit->callref & callref)) + break; + } + + return unit; +} + +static void mpt1327_go_idle(mpt1327_t *mpt1327); +static void mpt1327_release(mpt1327_unit_t *unit); + +/* Timeout handling */ +static void unit_timeout(struct timer *timer) +{ + mpt1327_unit_t *unit = (mpt1327_unit_t *)timer->priv; + + // FIXME: do some retry + switch (unit->state) { + case UNIT_CALLING_SAMIS: + if (unit->repeat) { + --unit->repeat; + PDEBUG(DMPT1327, DEBUG_INFO, "Resend AHYC, because unit timed out.\n"); + unit_new_state(unit, UNIT_CALLING_AHYC); + break; + } + PDEBUG(DMPT1327, DEBUG_INFO, "Unit failed to respond to AHYC, releasing...\n"); + mpt1327_release(unit); + if (unit->callref) { + call_up_release(unit->callref, CAUSE_NORMAL); + unit->callref = 0; + } + break; + case UNIT_CALLED_ACK: + if (unit->repeat) { + --unit->repeat; + PDEBUG(DMPT1327, DEBUG_INFO, "Resend AHY, because unit timed out.\n"); + unit_new_state(unit, UNIT_CALLED_AHY); + break; + } + PDEBUG(DMPT1327, DEBUG_INFO, "Unit failed to respond to AHY, releasing...\n"); + mpt1327_release(unit); + if (unit->callref) { + call_up_release(unit->callref, CAUSE_NORMAL); + unit->callref = 0; + } + break; + case UNIT_CALL: + PDEBUG(DMPT1327, DEBUG_NOTICE, "Release call, because unit timed out.\n"); + mpt1327_release(unit); + if (unit->callref) { + call_up_release(unit->callref, CAUSE_NORMAL); + unit->callref = 0; + } + break; + default: + PDEBUG(DMPT1327, DEBUG_ERROR, "Unknown timeout at state 0x%" PRIx64 ", please fix!\n", unit->state); + break; + } + +} + +void flush_units(void) +{ + mpt1327_unit_t *next; + + while (unit_list) { + next = unit_list->next; + timer_exit(&unit_list->timer); + free(unit_list); + unit_list = next; + } +} + +void dump_units(void) +{ + mpt1327_unit_t *unit = unit_list; + + PDEBUG(DDB, DEBUG_NOTICE, "Dump of Radio Unit list:\n"); + if (!unit) { + PDEBUG(DDB, DEBUG_NOTICE, " - No Radio Unit seen yet!\n"); + return; + } + + while (unit) { + PDEBUG(DDB, DEBUG_NOTICE, " - Radio Unit (Prefix:%d Ident:%d) seen on this TSC.\n", unit->prefix, unit->ident); + unit = unit->next; + } +} + +/* + * bands and channels + */ + +static struct mpt1327_band_def { + const char *name; + const char *description; +} mpt1327_band_def[] = { + { "MPT1343/1", "MPT1343 Sub Band 1"}, + { "MPT1343/2", "MPT1343 Sub Band 2"}, + { "Regionet43/1", "Regionet43 410-430 MHz (German band)" }, + { "Regionet43/2", "Regionet43 445-448 MHz (rarely used in Germany)" }, + { NULL, NULL } +}; + +const char *mpt1327_band_name(enum mpt1327_band band) +{ + return mpt1327_band_def[band].name; +} + +int mpt1327_band_by_short_name(const char *name) +{ + int i; + + for (i = 0; mpt1327_band_def[i].name; i++) { + if (!strcasecmp(mpt1327_band_def[i].name, name)) + return i; + } + + return -1; +} + +void mpt1327_band_list(void) +{ + int i; + + printf("Name\t\tDescription\n"); + printf("------------------------------------------------------------------------\n"); + for (i = 0; mpt1327_band_def[i].name; i++) + printf("%s\t%s\n", mpt1327_band_def[i].name, mpt1327_band_def[i].description); +} + +/* convert channel to frequency */ +double mpt1327_channel2freq(enum mpt1327_band band, int channel, int uplink) +{ + double freq = 0, offset = 0; // make GCC happy + int channels = 0; + + switch(band) { + case BAND_MPT1343_SUB1: + freq = 177.2125; + offset = 8.0; /* that's right! */ + channel -= 58; + channels = 503; + break; + case BAND_MPT1343_SUB2: + freq = 201.2125; + offset = -8.0; + channel -= 58; + channels = 503; + break; + case BAND_REGIONET43_SUB1: + freq = 420.0125; + offset = -10.0; + channel -= 1; + channels = 799; + break; + case BAND_REGIONET43_SUB2: + freq = 445.0125; + offset = -5.0; + channel -= 1; + channels = 239; + break; + } + + /* channel out of range */ + if (channel < 0 || channel > channels) + return 0.0; + + if (uplink == 2) + return offset * 1e6; + + freq += channel * 0.0125; + if (uplink) + freq += offset; + + return freq * 1e6; +} + +/* convert channel to chan field */ +uint16_t mpt1327_channel2chan(enum mpt1327_band band, int channel) +{ + uint16_t chan = 0; + + switch(band) { + case BAND_MPT1343_SUB1: + chan = channel - 58 + 513; + break; + case BAND_MPT1343_SUB2: + chan = channel - 58 + 1; + break; + case BAND_REGIONET43_SUB1: + chan = channel - 1 + 1; + break; + case BAND_REGIONET43_SUB2: + chan = channel - 1 + 1; // works with DETEWE + break; + } + + return chan; +} + +static struct mpt1327_channels { + enum mpt1327_chan_type chan_type; + const char *short_name; + const char *long_name; +} mpt1327_channels[] = { + { CHAN_TYPE_CC, "CC", "control channel" }, + { CHAN_TYPE_TC, "TC", "traffic channel" }, + { CHAN_TYPE_CC_TC, "CC/TC","combined control & traffic channel" }, + { 0, NULL, NULL } +}; + +void mpt1327_channel_list(void) +{ + int i; + + printf("Type\t\tDescription\n"); + printf("------------------------------------------------------------------------\n"); + for (i = 0; mpt1327_channels[i].long_name; i++) + printf("%s%s\t%s\n", mpt1327_channels[i].short_name, (strlen(mpt1327_channels[i].short_name) >= 8) ? "" : "\t", mpt1327_channels[i].long_name); +} + +int mpt1327_channel_by_short_name(const char *short_name) +{ + int i; + + for (i = 0; mpt1327_channels[i].short_name; i++) { + if (!strcasecmp(mpt1327_channels[i].short_name, short_name)) { + PDEBUG(DMPT1327, DEBUG_INFO, "Selecting channel '%s' = %s\n", mpt1327_channels[i].short_name, mpt1327_channels[i].long_name); + return mpt1327_channels[i].chan_type; + } + } + + return -1; +} + +const char *chan_type_short_name(enum mpt1327_chan_type chan_type) +{ + int i; + + for (i = 0; mpt1327_channels[i].short_name; i++) { + if (mpt1327_channels[i].chan_type == chan_type) + return mpt1327_channels[i].short_name; + } + + return "invalid"; +} + +const char *chan_type_long_name(enum mpt1327_chan_type chan_type) +{ + int i; + + for (i = 0; mpt1327_channels[i].long_name; i++) { + if (mpt1327_channels[i].chan_type == chan_type) + return mpt1327_channels[i].long_name; + } + + return "invalid"; +} + +/* + * MPT processing + */ + +static mpt1327_t *search_free_tc(void) +{ + sender_t *sender; + mpt1327_t *tc, *cc_tc = NULL; + + for (sender = sender_head; sender; sender = sender->next) { + tc = (mpt1327_t *) sender; + if (tc->state != STATE_IDLE) + continue; + /* remember combined voice/control/paging channel as second alternative */ + if (tc->chan_type == CHAN_TYPE_CC_TC) + cc_tc = tc; + if (tc->chan_type == CHAN_TYPE_TC) + return tc; + } + + return cc_tc; + +} + +static mpt1327_t *search_cc(void) +{ + sender_t *sender; + mpt1327_t *cc = NULL; + + for (sender = sender_head; sender; sender = sender->next) { + cc = (mpt1327_t *) sender; + /* remember combined voice/control/paging channel as second alternative */ + if (cc->chan_type == CHAN_TYPE_CC_TC) + return cc; + if (cc->chan_type == CHAN_TYPE_CC) + return cc; + } + + return NULL; + +} + +const char *mpt1327_state_name(enum mpt1327_state state) +{ + static char invalid[16]; + + switch (state) { + case STATE_NULL: + return "(NULL)"; + case STATE_IDLE: + return "IDLE"; + case STATE_BUSY: + return "BUSY"; + } + + sprintf(invalid, "invalid(%d)", state); + return invalid; +} + +void mpt1327_display_status(void) +{ + sender_t *sender; + mpt1327_t *mpt1327; + + display_status_start(); + for (sender = sender_head; sender; sender = sender->next) { + mpt1327 = (mpt1327_t *) sender; + display_status_channel(mpt1327->sender.kanal, chan_type_short_name(mpt1327->chan_type), mpt1327_state_name(mpt1327->state)); + if (mpt1327->unit) { + char unit_id[32]; + sprintf(unit_id, "%d/%d", mpt1327->unit->prefix, mpt1327->unit->ident); + display_status_subscriber(unit_id, NULL); + } + } + display_status_end(); +} + +static void mpt1327_new_state(mpt1327_t *mpt1327, enum mpt1327_state new_state, mpt1327_unit_t *unit) +{ + if (mpt1327->state == new_state) + return; + PDEBUG_CHAN(DMPT1327, DEBUG_DEBUG, "State change: %s -> %s\n", mpt1327_state_name(mpt1327->state), mpt1327_state_name(new_state)); + + /* unlink unit, if linked */ + if (mpt1327->unit) { + mpt1327->unit->tc = NULL; + mpt1327->unit = NULL; + } + + /* link unit, if given */ + if (unit) { + unit->tc = mpt1327; + mpt1327->unit = unit; + } + + mpt1327->state = new_state; + mpt1327_display_status(); +} + +static void mpt1327_timeout(struct timer *timer); + +/* Create transceiver instance and link to a list. */ +int mpt1327_create(enum mpt1327_band band, const char *kanal, enum mpt1327_chan_type chan_type, const char *audiodev, int use_sdr, int samplerate, double rx_gain, double tx_gain, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, double squelch_db) +{ + sender_t *sender; + mpt1327_t *mpt1327; + int rc; + + /* check channel matching and set deviation factor */ + if (mpt1327_channel2freq(band, atoi(kanal), 0) == 0.0) + return -EINVAL; + + for (sender = sender_head; sender; sender = sender->next) { + mpt1327 = (mpt1327_t *)sender; + if ((mpt1327->chan_type == CHAN_TYPE_CC || mpt1327->chan_type == CHAN_TYPE_CC_TC) + && (chan_type == CHAN_TYPE_CC || chan_type == CHAN_TYPE_CC_TC)) { + PDEBUG(DCNETZ, DEBUG_NOTICE, "More than one control channel is not supported, please define other channels as traffic channels!\n"); + return -EINVAL; + } + } + mpt1327 = calloc(1, sizeof(mpt1327_t)); + if (!mpt1327) { + PDEBUG(DMPT1327, DEBUG_ERROR, "No memory!\n"); + return -EIO; + } + + PDEBUG(DMPT1327, DEBUG_DEBUG, "Creating 'MPT1327' instance for Channel %s on Band %s (sample rate %d).\n", kanal, mpt1327_band_def[band].name, samplerate); + + /* init general part of transceiver */ + rc = sender_create(&mpt1327->sender, kanal, mpt1327_channel2freq(band, atoi(kanal), 0), mpt1327_channel2freq(band, atoi(kanal), 1), audiodev, use_sdr, samplerate, rx_gain, tx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE); + if (rc < 0) { + PDEBUG(DMPT1327, DEBUG_ERROR, "Failed to init 'Sender' processing!\n"); + goto error; + } + + /* init audio processing */ + rc = dsp_init_sender(mpt1327, squelch_db); + if (rc < 0) { + PDEBUG(DANETZ, DEBUG_ERROR, "Failed to init signal processing!\n"); + goto error; + } + + /* timers */ + timer_init(&mpt1327->timer, mpt1327_timeout, mpt1327); + + mpt1327->band = band; + mpt1327->chan_type = chan_type; + + /* only accept these valued */ + if (sysdef.framelength != 1 && sysdef.framelength != 3 && sysdef.framelength != 6) { + PDEBUG(DMPT1327, DEBUG_ERROR, "Invalid frame length %d, please fix!\n", sysdef.framelength); + abort(); + } + if (sysdef.wt != 5 && sysdef.wt != 10 && sysdef.wt != 15) { + PDEBUG(DMPT1327, DEBUG_ERROR, "Invalid WT value %d, please fix!\n", sysdef.wt); + abort(); + } + + /* go into idle state */ + mpt1327_go_idle(mpt1327); + + PDEBUG(DMPT1327, DEBUG_NOTICE, "Created channel #%s of type '%s' = %s\n", kanal, chan_type_short_name(chan_type), chan_type_long_name(chan_type)); + + return 0; + +error: + mpt1327_destroy(&mpt1327->sender); + + return rc; +} + +void mpt1327_check_channels(void) +{ + sender_t *sender; + mpt1327_t *mpt1327; + int cc = 0, tc = 0; + int note = 0; + + for (sender = sender_head; sender; sender = sender->next) { + mpt1327 = (mpt1327_t *) sender; + if (mpt1327->chan_type == CHAN_TYPE_CC) + cc = 1; + if (mpt1327->chan_type == CHAN_TYPE_TC) + tc = 1; + if (mpt1327->chan_type == CHAN_TYPE_CC_TC) { + cc = 1; + tc = 1; + } + } + if (cc && !tc) { + PDEBUG(DMPT1327, DEBUG_NOTICE, "\n"); + PDEBUG(DMPT1327, DEBUG_NOTICE, "*** Selected channel(s) can be used for control only.\n"); + PDEBUG(DMPT1327, DEBUG_NOTICE, "*** No call is possible.\n"); + PDEBUG(DMPT1327, DEBUG_NOTICE, "*** Use at least one 'TC'!\n"); + note = 1; + } + if (tc && !cc) { + PDEBUG(DMPT1327, DEBUG_NOTICE, "\n"); + PDEBUG(DMPT1327, DEBUG_NOTICE, "*** Selected channel(s) can be used for traffic only.\n"); + PDEBUG(DMPT1327, DEBUG_NOTICE, "*** No call to the mobile phone is possible.\n"); + PDEBUG(DMPT1327, DEBUG_NOTICE, "*** Use one 'CC'!\n"); + note = 1; + } + if (note) + PDEBUG(DMPT1327, DEBUG_NOTICE, "\n"); +} + +/* Destroy transceiver instance and unlink from list. */ +void mpt1327_destroy(sender_t *sender) +{ + mpt1327_t *mpt1327 = (mpt1327_t *) sender; + + PDEBUG(DMPT1327, DEBUG_DEBUG, "Destroying 'MPT1327' instance for channel = %s.\n", sender->kanal); + + dsp_cleanup_sender(mpt1327); + timer_exit(&mpt1327->timer); + sender_destroy(&mpt1327->sender); + free(sender); +} + +/* Abort connection towards mobile station changing to IDLE state */ +static void mpt1327_go_idle(mpt1327_t *mpt1327) +{ + timer_stop(&mpt1327->timer); + mpt1327->pressel_on = 0; + + PDEBUG(DMPT1327, DEBUG_INFO, "Entering IDLE state on channel %s.\n", mpt1327->sender.kanal); + mpt1327_new_state(mpt1327, STATE_IDLE, NULL); + memset(&mpt1327->tx_sched, 0, sizeof(mpt1327->tx_sched)); + switch (mpt1327->chan_type) { + case CHAN_TYPE_CC: + case CHAN_TYPE_CC_TC: + mpt1327->tx_sched.state = SCHED_STATE_CC_IDLE; + mpt1327_set_dsp_mode(mpt1327, DSP_MODE_CONTROL, 0); + break; + case CHAN_TYPE_TC: + mpt1327->tx_sched.state = SCHED_STATE_TC_IDLE; + mpt1327_set_dsp_mode(mpt1327, DSP_MODE_OFF, 0); + break; + } +} + +static void mpt1327_release(mpt1327_unit_t *unit) +{ + timer_stop(&unit->timer); + + if (unit->state == UNIT_CALL && unit->tc) { + /* release all units on traffic channel */ + unit_new_state(unit, UNIT_CALL_CLEAR); + unit->repeat = REPEAT_CLEAR; + } else { + /* release unit on control channel */ + unit_new_state(unit, UNIT_CALLED_AHYX); + unit->repeat = REPEAT_AHYX; + } +} + +static int gtc_aloha_number(int length) +{ + switch (length) { + case 1: + return 1; + case 3: + return 2; + case 6: + return 3; + default: + return 0; + } +} + +/* schedule message on control channel + * + * on IDLE state a STARTUP sequence is sent, followed by alternating CCSC and + * ADDR codewords. if a unit has nothing to send, ALH is transmitted. if a + * unit wants to send a message, the message is scheduled. this message can + * be repeated. afterwards, if no unit has something to send, the ALH is + * scheduled. + * + * a dummy slot is used to allow radio unit to allow multi slot response to a + * request from TSC. + */ +int mpt1327_send_codeword_control(mpt1327_t *mpt1327, mpt1327_codeword_t *codeword) +{ + mpt1327_unit_t *unit; + + /* CC scheduler, the sched_state is what we have sent */ + switch (mpt1327->tx_sched.state) { + case SCHED_STATE_CC_ADDR: + codeword->type = MPT_CCSC; + codeword->params[MPT_SYS] = sysdef.sys; + mpt1327->tx_sched.state = SCHED_STATE_CC_CCSC; + break; + case SCHED_STATE_CC_STARTUP: + case SCHED_STATE_CC_CCSC: + /* count slots for each broadcast */ + if (mpt1327->tx_sched.bcast_count < sysdef.bcast_slots) { + mpt1327->tx_sched.bcast_count++; + } + /* count slots in frame */ + if (!mpt1327->tx_sched.frame_length || mpt1327->tx_sched.frame_count == mpt1327->tx_sched.frame_length) { + mpt1327->tx_sched.frame_length = sysdef.framelength; + mpt1327->tx_sched.frame_count = 0; + } + /* send out a dummy slot to prevent random access from other units */ + if (mpt1327->tx_sched.dummy_slot) { + mpt1327->tx_sched.dummy_slot--; + /* if this is a new frame, make it 1 slot long */ + if (mpt1327->tx_sched.frame_count == 0) + mpt1327->tx_sched.frame_length = 1; + codeword->type = MPT_AHY; + codeword->params[MPT_PFIX] = 0x2a; /* just some alternating pattern (ignored) */ + codeword->params[MPT_IDENT1] = IDENT_DUMMYI; + codeword->params[MPT_IDENT2] = IDENT_DUMMYI; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending dummy AHY, to prevent random access while receiving SAMIS\n"); + } else { + unit = find_unit_state(UNIT_REGISTER_ACK | UNIT_DIVERSION_REJ | UNIT_CALLING_REJ | UNIT_CALLING_AHYC | UNIT_CALLED_AHY | UNIT_GTC_P | UNIT_GTC_A | UNIT_GTC_B | UNIT_CANCEL_ACK | UNIT_CALLED_AHYX, NULL); + if (!unit) { + /* if we reached the slot count for broadcast message + * AND the frame is not frame 0 (with given length, as long es frame length > 1) + * if the frame length is 1 (implies count == 0), then make this message single slot frame + */ + if (mpt1327->tx_sched.bcast_count == sysdef.bcast_slots + && (mpt1327->tx_sched.frame_count > 0 || mpt1327->tx_sched.frame_length == 1)) { + mpt1327->tx_sched.bcast_count = 0; + /* if this is a new frame, make it 1 slot long */ + if (mpt1327->tx_sched.frame_count == 0) + mpt1327->tx_sched.frame_length = 1; + codeword->type = MPT_BCAST2; + codeword->params[MPT_SYS] = sysdef.sys; + codeword->params[MPT_IVAL] = sysdef.per; + if (!sysdef.per) + codeword->params[MPT_PER] = 1; + if (!sysdef.pon) + codeword->params[MPT_PON] = 1; + } else { + codeword->type = MPT_ALH; + codeword->params[MPT_PFIX] = 0x2a; /* just some alternating pattern (ignored) */ + codeword->params[MPT_IDENT1] = 0x1555; /* dito */ + codeword->params[MPT_CHAN4] = mpt1327_channel2chan(mpt1327->band, atoi(mpt1327->sender.kanal)) & 0xf; + codeword->params[MPT_WT] = (sysdef.wt < 5) ? sysdef.wt : sysdef.wt / 5 + 4; + } + } else switch (unit->state) { + case UNIT_REGISTER_ACK: /* ack to register */ + codeword->type = MPT_ACK; + codeword->params[MPT_PFIX] = unit->prefix; + codeword->params[MPT_IDENT1] = IDENT_REGI; + codeword->params[MPT_IDENT2] = unit->ident; + codeword->params[MPT_QUAL] = 0; + unit_new_state(unit, UNIT_IDLE); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending acknowledge to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident); + break; + case UNIT_DIVERSION_REJ: /* reject diversion */ + codeword->type = MPT_ACKX; + codeword->params[MPT_PFIX] = unit->prefix; + codeword->params[MPT_IDENT1] = unit->called_ident; + codeword->params[MPT_IDENT2] = unit->ident; + codeword->params[MPT_QUAL] = 0; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending negative acknowledge to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident); + if (unit->repeat) { + --unit->repeat; + break; + } + unit_new_state(unit, UNIT_IDLE); + if (unit->tc) + mpt1327_go_idle(unit->tc); + break; + case UNIT_CALLING_REJ: /* outgoing call rejected, no channel available */ + codeword->type = MPT_ACKX; + codeword->params[MPT_PFIX] = unit->prefix; + codeword->params[MPT_IDENT1] = unit->called_ident; + codeword->params[MPT_IDENT2] = unit->ident; + codeword->params[MPT_QUAL] = 1; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending negative acknowledge to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident); + if (unit->repeat) { + --unit->repeat; + break; + } + unit_new_state(unit, UNIT_IDLE); + if (unit->tc) + mpt1327_go_idle(unit->tc); + break; + case UNIT_CALLING_AHYC: /* request SAMIS for dialing data */ + codeword->type = MPT_AHYC; + codeword->params[MPT_PFIX] = unit->prefix; + codeword->params[MPT_IDENT1] = unit->called_ident; + codeword->params[MPT_IDENT2] = unit->ident; /* implies Mode 1 */ + switch (unit->called_type) { + case CALLED_TYPE_INTERPFX: + codeword->params[MPT_SLOTS] = 0x1; + codeword->params[MPT_DESC] = 0x0; + break; + case CALLED_TYPE_PSTN_LONG1: + codeword->params[MPT_SLOTS] = 0x1; + codeword->params[MPT_DESC] = 0x1; + break; + case CALLED_TYPE_PSTN_LONG2: + codeword->params[MPT_SLOTS] = 0x2; + codeword->params[MPT_DESC] = 0x1; + mpt1327->tx_sched.dummy_slot = 1; + break; + case CALLED_TYPE_PBX_LONG: + codeword->params[MPT_SLOTS] = 0x1; + codeword->params[MPT_DESC] = 0x2; + break; + default: + PDEBUG_CHAN(DMPT1327, DEBUG_ERROR, "Want to send AHYC, but called_type not set correctly, please fix!\n"); + abort(); + } + unit_new_state(unit, UNIT_CALLING_SAMIS); + mpt1327->rx_sched.data_prefix = unit->prefix; + mpt1327->rx_sched.data_ident = unit->ident; + PDEBUG_CHAN(DMPT1327, DEBUG_DEBUG, "Starting timer, waiting for response\n"); + timer_start(&unit->timer, RESPONSE_TIMEOUT); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending AHYC, to request SAMIS from Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident); + break; + case UNIT_CALLED_AHY: /* call to unit and request ACK from unit */ + codeword->type = MPT_AHY; + codeword->params[MPT_PFIX] = unit->prefix; + codeword->params[MPT_IDENT1] = unit->ident; + codeword->params[MPT_IDENT2] = IDENT_PABXI; + codeword->params[MPT_D] = 0; /* speech call */ + codeword->params[MPT_POINT] = 0; /* demand ACK from ident1 */ + codeword->params[MPT_CHECK] = 1; /* unit is in contact and accepts calls */ + codeword->params[MPT_E] = 0; /* no emergency call */ + codeword->params[MPT_AD] = 0; /* no appended data */ + unit_new_state(unit, UNIT_CALLED_ACK); + PDEBUG_CHAN(DMPT1327, DEBUG_DEBUG, "Starting timer, waiting for response\n"); + timer_start(&unit->timer, RESPONSE_TIMEOUT); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending AHY, to request ACK from Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident); + break; + case UNIT_GTC_P: /* channel assignment to unit itself and called unit */ + codeword->type = MPT_GTC; + codeword->params[MPT_PFIX] = unit->prefix; + codeword->params[MPT_IDENT1] = unit->called_ident; + codeword->params[MPT_IDENT2] = unit->ident; + codeword->params[MPT_CHAN] = mpt1327_channel2chan(unit->tc->band, atoi(unit->tc->sender.kanal)); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending channel assignment to calling and called Radio Units (Prefix:%d Ident:%d and Ident:%d)\n", unit->prefix, unit->ident, unit->called_ident); + if (unit->repeat) { + --unit->repeat; + break; + } + unit_new_state(unit, UNIT_CALL); + mpt1327_set_dsp_mode(unit->tc, DSP_MODE_TRAFFIC, 1); + if (sysdef.timeout) + timer_start(&unit->timer, sysdef.timeout); + break; + case UNIT_GTC_B: /* channel assignment to called unit */ + /* NOTE GTC to called unit must be sent before GTC to calling unit (1.3.5.3) */ + codeword->type = MPT_GTC; + codeword->params[MPT_PFIX] = unit->prefix; + codeword->params[MPT_IDENT1] = unit->called_ident; + codeword->params[MPT_IDENT2] = IDENT_DUMMYI; + codeword->params[MPT_CHAN] = mpt1327_channel2chan(unit->tc->band, atoi(unit->tc->sender.kanal)); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending channel assignment to called Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident); + if (unit->repeat) { + --unit->repeat; + break; + } + unit_new_state(unit, UNIT_GTC_A); + unit->repeat = REPEAT_GTC; + break; + case UNIT_GTC_A: /* channel assignment unit itself */ + codeword->type = MPT_GTC; + codeword->params[MPT_PFIX] = unit->prefix; + codeword->params[MPT_IDENT1] = IDENT_DUMMYI; + codeword->params[MPT_IDENT2] = unit->ident; + codeword->params[MPT_CHAN] = mpt1327_channel2chan(unit->tc->band, atoi(unit->tc->sender.kanal)); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending channel assignment to calling Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident); + if (unit->repeat) { + --unit->repeat; + break; + } + unit_new_state(unit, UNIT_CALL); + mpt1327_set_dsp_mode(unit->tc, DSP_MODE_TRAFFIC, 1); + if (sysdef.timeout) + timer_start(&unit->timer, sysdef.timeout); + break; + case UNIT_CANCEL_ACK: + codeword->type = MPT_ACK; + codeword->params[MPT_PFIX] = unit->called_prefix; + codeword->params[MPT_IDENT1] = unit->called_ident; + codeword->params[MPT_IDENT2] = unit->ident; + codeword->params[MPT_QUAL] = 1; + unit_new_state(unit, UNIT_IDLE); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending acknowledge to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident); + break; + case UNIT_CALLED_AHYX: /* channel assignment unit itself */ + codeword->type = MPT_AHYX; + codeword->params[MPT_PFIX] = unit->prefix; + codeword->params[MPT_IDENT1] = unit->ident; + codeword->params[MPT_IDENT2] = IDENT_PABXI; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending AHYX, to cancel call to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident); + if (unit->repeat) { + --unit->repeat; + break; + } + unit_new_state(unit, UNIT_IDLE); + if (unit->tc) + mpt1327_go_idle(unit->tc); + break; + } + } + if (codeword->type == MPT_GTC) + codeword->params[MPT_N] = (mpt1327->tx_sched.frame_count == 0) ? gtc_aloha_number(mpt1327->tx_sched.frame_length) : 0; + else + codeword->params[MPT_N] = (mpt1327->tx_sched.frame_count == 0) ? mpt1327->tx_sched.frame_length : 0; + mpt1327->tx_sched.frame_count++; + mpt1327->tx_sched.state = SCHED_STATE_CC_ADDR; + break; + default: + /* on dirty state (e.g. changing from TC to CC), we start control channel framing */ + codeword->type = MPT_START_SYNC; + mpt1327->tx_sched.frame_length = 0; + mpt1327->tx_sched.state = SCHED_STATE_CC_STARTUP; + break; + } + + return 0; +} + +/* schedule messages on traffic channel + * + * when the unit has a message to send, it, the repeat counter is decreased and + * a SYNC is sent the next request will send the an ADDR codeword. this will + * repeat until the repeat counter reaches 0. + */ +int mpt1327_send_codeword_traffic(mpt1327_t *mpt1327, mpt1327_codeword_t __attribute__((unused)) *codeword) +{ + mpt1327_unit_t *unit; + mpt1327_t *cc; + + /* TC scheduler */ + switch (mpt1327->tx_sched.state) { + case SCHED_STATE_TC_IDLE: + case SCHED_STATE_TC_ADDR: + /* on idle state or after sending address, we search for a unit that wants to send a message */ + unit = find_unit_state(UNIT_CALL_CLEAR, mpt1327); + if (!unit) { + /* no message, so we have nothing to send */ + mpt1327->tx_sched.state = SCHED_STATE_TC_IDLE; + return -1; + } + switch (unit->state) { + case UNIT_CALL_CLEAR: /* release channel */ + if (!unit->repeat) { + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Done sending clear down on traffic channel, releasing\n"); + unit_new_state(unit, UNIT_IDLE); + mpt1327_go_idle(mpt1327); + return -1; + } + --unit->repeat; + break; + } + codeword->type = MPT_START_SYNT; + mpt1327->tx_sched.state = SCHED_STATE_TC_SYNT; + break; + case SCHED_STATE_TC_SYNT: + /* after sending SYNT, we process message that unit wants to send */ + unit = find_unit_state(UNIT_CALL_CLEAR, mpt1327); + if (!unit) { + mpt1327->tx_sched.state = SCHED_STATE_TC_IDLE; + return -1; + } + switch (unit->state) { + case UNIT_CALL_CLEAR: /* release channel */ + codeword->type = MPT_CLEAR; + codeword->params[MPT_CHAN] = mpt1327_channel2chan(mpt1327->band, atoi(mpt1327->sender.kanal)); + cc = search_cc(); + if (cc) + codeword->params[MPT_CONT] = mpt1327_channel2chan(cc->band, atoi(cc->sender.kanal)); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Sending clear down on traffic channel\n"); + } + mpt1327->tx_sched.state = SCHED_STATE_TC_ADDR; + break; + default: + /* on dirty state (e.g. changing from CC to TC), we enter idle state */ + mpt1327->tx_sched.state = SCHED_STATE_TC_IDLE; + return -1; + } + + return 0; +} + +int mpt1327_send_codeword(mpt1327_t *mpt1327, uint64_t *bits) +{ + mpt1327_codeword_t codeword; + int rc = -1; + + memset(&codeword, 0, sizeof(codeword)); + + switch (mpt1327->dsp_mode) { + case DSP_MODE_CONTROL: + rc = mpt1327_send_codeword_control(mpt1327, &codeword); + break; + case DSP_MODE_TRAFFIC: + rc = mpt1327_send_codeword_traffic(mpt1327, &codeword); + break; + default: + ; + } + + if (rc < 0) + return 0; + + *bits = mpt1327_encode_codeword(&codeword); + return 64; +} + +static void out_setup(mpt1327_unit_t *unit, uint8_t network_type, int network_id) +{ + char caller_id[32], id[16]; + + /* setup call */ + PDEBUG(DMPT1327, DEBUG_INFO, "Setup call to network.\n"); + sprintf(caller_id, "%03d%04d", unit->prefix, unit->ident); + if (network_id) + sprintf(id, "%d", network_id); + else + id[0] = '\0'; + unit->callref = call_up_setup(caller_id, unit->called_number, network_type, id); +} + +static void _cancel_pending_call(mpt1327_t *mpt1327, mpt1327_unit_t *unit) +{ + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "We are already in a call, the phone might have restarted, so we free old channel first.\n"); + mpt1327_go_idle(unit->tc); + timer_stop(&unit->timer); + if (unit->callref) { + call_up_release(unit->callref, CAUSE_NORMAL); + unit->callref = 0; + } +} + +void mpt1327_receive_codeword_control(mpt1327_t *mpt1327, mpt1327_codeword_t *codeword) +{ + mpt1327_unit_t *unit; + mpt1327_t *tc; + + switch (codeword->type) { + case MPT_RQR: /* register */ + mpt1327_reset_sync(mpt1327); /* message complete */ + unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT1]); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) registers\n", unit->prefix, unit->ident); + if (unit->tc) + _cancel_pending_call(mpt1327, unit); + unit_new_state(unit, UNIT_REGISTER_ACK); + break; + case MPT_RQT: /* diversion */ + mpt1327_reset_sync(mpt1327); /* message complete */ + unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT2]); + unit->called_ident = codeword->params[MPT_IDENT1]; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) requests diversion\n", unit->prefix, unit->ident); + if (unit->tc) + _cancel_pending_call(mpt1327, unit); + PDEBUG_CHAN(DMPT1327, DEBUG_NOTICE, "Diversion not supported by TSC, rejecting...\n"); + unit_new_state(unit, UNIT_DIVERSION_REJ); + break; + case MPT_RQS: /* simple call */ + case MPT_RQE: /* emergency call */ + mpt1327_reset_sync(mpt1327); /* message complete */ + unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT2]); + unit->called_ident = codeword->params[MPT_IDENT1]; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Ident:%d%s\n", unit->prefix, unit->ident, unit->called_ident, (codeword->type == MPT_RQE) ? " (emergency)" : ""); + if (unit->tc) + _cancel_pending_call(mpt1327, unit); + tc = search_free_tc(); + if (!tc) { + unit_new_state(unit, UNIT_CALLING_REJ); + PDEBUG_CHAN(DMPT1327, DEBUG_NOTICE, "No free Traffic Channel, call is rejected.\n"); + break; + } + if (codeword->params[MPT_EXT]) { + int exchange; + unit->called_type = CALLED_TYPE_PBX_SHORT; + sprintf(unit->called_number, "%d", unit->called_ident); + exchange = ((codeword->params[MPT_FLAG1] << 1) | codeword->params[MPT_FLAG2]) + 1; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, " -> Call to PBX exchange %d, Number %s\n", exchange, unit->called_number); + unit_new_state(unit, UNIT_GTC_A); + unit->repeat = REPEAT_GTC; + out_setup(unit, OSMO_CC_NETWORK_MPT1327_PBX, exchange); + } else if (unit->called_ident >= IDENT_PSTNSI1 && unit->called_ident < IDENT_PSTNSI1 + 15) { + unit->called_type = CALLED_TYPE_PSTN_PRE; + sprintf(unit->called_number, "%d", unit->called_ident - IDENT_PSTNSI1 + 1); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, " -> Call to PSTN with pre-arranged Number %s\n", unit->called_number); + unit_new_state(unit, UNIT_GTC_A); + unit->repeat = REPEAT_GTC; + out_setup(unit, OSMO_CC_NETWORK_MPT1327_PSTN, 0); + } else switch (unit->called_ident) { + case IDENT_IPFIXI: + unit->called_type = CALLED_TYPE_INTERPFX; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, " -> Call to Unit/Group %d with different Prefix\n", unit->called_ident); + unit_new_state(unit, UNIT_CALLING_AHYC); + unit->repeat = REPEAT_AHYC; + break; + case IDENT_ALLI: + unit->called_type = CALLED_TYPE_SYSTEM; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, " -> System wide Call\n"); + unit_new_state(unit, UNIT_GTC_P); + unit->repeat = REPEAT_GTC; + break; + case IDENT_PSTNGI: + if (codeword->params[MPT_FLAG1]) { + unit->called_type = CALLED_TYPE_PSTN_LONG2; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, " -> Call to PSTN with long Number (10..31 Digits)\n"); + } else { + unit->called_type = CALLED_TYPE_PSTN_LONG1; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, " -> Call to PSTN with long Number (1..9 Digits)\n"); + } + unit_new_state(unit, UNIT_CALLING_AHYC); + unit->repeat = REPEAT_AHYC; + break; + case IDENT_PABXI: + unit->called_type = CALLED_TYPE_PBX_LONG; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, " -> Call to PBX (long number)\n"); + unit_new_state(unit, UNIT_CALLING_AHYC); + unit->repeat = REPEAT_AHYC; + break; + default: + unit->called_type = CALLED_TYPE_UNIT; + unit->called_prefix = unit->prefix; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, " -> Call to Unit/Group %d (same Prefix)\n", unit->called_ident); + unit_new_state(unit, UNIT_GTC_P); + unit->repeat = REPEAT_GTC; + } + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Allocating Traffic Channel %s\n", tc->sender.kanal); + mpt1327_new_state(tc, STATE_BUSY, unit); + break; + case MPT_SAMIS: /* SAMIS response */ + unit = get_unit(mpt1327->rx_sched.data_prefix, mpt1327->rx_sched.data_ident); + if (unit->state != UNIT_CALLING_SAMIS) { + PDEBUG_CHAN(DMPT1327, DEBUG_ERROR, "Radio Unit (Prefix:%d Ident:%d) sends SAMIS, but not requested\n", unit->prefix, unit->ident); + break; + } + switch (unit->called_type) { + case CALLED_TYPE_INTERPFX: + if (codeword->params[MPT_DESC] != 0x0) { + PDEBUG_CHAN(DMPT1327, DEBUG_ERROR, "Expecting DESC=%d from Radio Unit, but got DESC=%d, dropping!\n", 0x0, (int)codeword->params[MPT_DESC]); + return; + } + unit->called_prefix = codeword->params[MPT_PARAMETERS1] >> 13; + unit->called_ident = codeword->params[MPT_PARAMETERS1] & 0x1fff; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Prefix:%d Ident:%d\n", unit->prefix, unit->ident, unit->called_prefix, unit->called_ident); + unit_new_state(unit, UNIT_GTC_B); + unit->repeat = REPEAT_GTC; + break; + case CALLED_TYPE_PSTN_LONG1: + case CALLED_TYPE_PSTN_LONG2: + if (codeword->params[MPT_DESC] != 0x1) { + PDEBUG_CHAN(DMPT1327, DEBUG_ERROR, "Expecting DESC=%d from Radio Unit, but got DESC=%d, dropping!\n", 0x1, (int)codeword->params[MPT_DESC]); + return; + } + unit->called_number[0] = '0'; + unit->called_number[1] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 16) & 0xf]; + unit->called_number[2] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 12) & 0xf]; + unit->called_number[3] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 8) & 0xf]; + unit->called_number[4] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 4) & 0xf]; + unit->called_number[5] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 0) & 0xf]; + unit->called_number[6] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 12) & 0xf]; + unit->called_number[7] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 8) & 0xf]; + unit->called_number[8] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 4) & 0xf]; + unit->called_number[9] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 0) & 0xf]; + unit->called_number[10] = '\0'; + /* schedule reception of one or two words */ + mpt1327->rx_sched.data_num = codeword->params[MPT_PARAMETERS2] >> 16; + mpt1327->rx_sched.data_count = 0; + mpt1327->rx_sched.data_word = MPT_SAMIS_DT; + if (mpt1327->rx_sched.data_num == 0) { + timer_stop(&unit->timer); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Number %s\n", unit->prefix, unit->ident, unit->called_number); + out_setup(unit, OSMO_CC_NETWORK_MPT1327_PSTN, 0); + unit_new_state(unit, UNIT_GTC_A); + unit->repeat = REPEAT_GTC; + } + break; + case CALLED_TYPE_PBX_LONG: + if (codeword->params[MPT_DESC] != 0x2) { + PDEBUG_CHAN(DMPT1327, DEBUG_ERROR, "Expecting DESC=%d from Radio Unit, but got DESC=%d, dropping!\n", 0x2, (int)codeword->params[MPT_DESC]); + return; + } + unit->called_number[0] = '0'; + unit->called_number[1] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 16) & 0xf]; + unit->called_number[2] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 12) & 0xf]; + unit->called_number[3] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 8) & 0xf]; + unit->called_number[4] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 4) & 0xf]; + unit->called_number[5] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS1] >> 0) & 0xf]; + unit->called_number[6] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 12) & 0xf]; + unit->called_number[7] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 8) & 0xf]; + unit->called_number[8] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 4) & 0xf]; + unit->called_number[9] = mpt1327_bcd[(codeword->params[MPT_PARAMETERS2] >> 0) & 0xf]; + unit->called_number[10] = '\0'; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Number %s\n", unit->prefix, unit->ident, unit->called_number); + timer_stop(&unit->timer); + out_setup(unit, OSMO_CC_NETWORK_MPT1327_PBX, 0); + unit_new_state(unit, UNIT_GTC_A); + unit->repeat = REPEAT_GTC; + break; + default: + PDEBUG_CHAN(DMPT1327, DEBUG_ERROR, "Want to receive SAMIS, but called_type not set correctly, please fix!\n"); + abort(); + } + break; + case MPT_SAMIS_DT: + unit = get_unit(mpt1327->rx_sched.data_prefix, mpt1327->rx_sched.data_ident); + if (unit->state != UNIT_CALLING_SAMIS) { + PDEBUG_CHAN(DMPT1327, DEBUG_ERROR, "Radio Unit (Prefix:%d Ident:%d) sends SAMIS, but not requested\n", unit->prefix, unit->ident); + break; + } + if (mpt1327->rx_sched.data_count == 1) { + unit->called_number[10] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 40) & 0xf]; + unit->called_number[11] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 36) & 0xf]; + unit->called_number[12] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 32) & 0xf]; + unit->called_number[13] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 28) & 0xf]; + unit->called_number[14] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 24) & 0xf]; + unit->called_number[15] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 20) & 0xf]; + unit->called_number[16] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 16) & 0xf]; + unit->called_number[17] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 12) & 0xf]; + unit->called_number[18] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 8) & 0xf]; + unit->called_number[19] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 4) & 0xf]; + unit->called_number[20] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 0) & 0xf]; + unit->called_number[21] = '\0'; + if (mpt1327->rx_sched.data_num == 1) { + timer_stop(&unit->timer); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Number %s\n", unit->prefix, unit->ident, unit->called_number); + out_setup(unit, OSMO_CC_NETWORK_MPT1327_PSTN, 0); + unit_new_state(unit, UNIT_GTC_A); + unit->repeat = REPEAT_GTC; + } + } else { + unit->called_number[21] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 40) & 0xf]; + unit->called_number[22] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 36) & 0xf]; + unit->called_number[23] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 32) & 0xf]; + unit->called_number[24] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 28) & 0xf]; + unit->called_number[25] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 24) & 0xf]; + unit->called_number[26] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 20) & 0xf]; + unit->called_number[27] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 16) & 0xf]; + unit->called_number[28] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 12) & 0xf]; + unit->called_number[29] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 8) & 0xf]; + unit->called_number[30] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 4) & 0xf]; + unit->called_number[31] = mpt1327_bcd[(codeword->params[MPT_BCD11] >> 0) & 0xf]; + unit->called_number[32] = '\0'; + mpt1327->rx_sched.data_num = 0; /* just in case it is more than 2 data words */ + timer_stop(&unit->timer); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) calls Number %s\n", unit->prefix, unit->ident, unit->called_number); + out_setup(unit, OSMO_CC_NETWORK_MPT1327_PSTN, 0); + unit_new_state(unit, UNIT_GTC_A); + unit->repeat = REPEAT_GTC; + } + break; + case MPT_RQX: /* call cancel */ + unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT2]); + unit->called_ident = codeword->params[MPT_IDENT1]; + timer_stop(&unit->timer); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) cancels call to %d\n", unit->prefix, unit->ident, unit->called_ident); + unit_new_state(unit, UNIT_CANCEL_ACK); + if (unit->tc) { + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Free Traffic Channel %s, because unit cancels on control channel\n", unit->tc->sender.kanal); + mpt1327_go_idle(unit->tc); + } + break; + case MPT_ACKI: /* ack from unit (not ready, wait for RQQ) */ + unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT1]); + timer_stop(&unit->timer); + if (unit->state == UNIT_CALLED_ACK) { + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) acknowledges call (not yet ready, waiting for RQQ\n", unit->prefix, unit->ident); + if (unit->callref) + call_up_alerting(unit->callref); + break; + } + PDEBUG_CHAN(DMPT1327, DEBUG_ERROR, "Radio Unit (Prefix:%d Ident:%d) acknowledges, no call\n", unit->prefix, unit->ident); + break; + case MPT_ACK: /* ack from unit */ + unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT1]); + timer_stop(&unit->timer); + if (unit->state == UNIT_CALLED_ACK) { + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) acknowledges call\n", unit->prefix, unit->ident); +answer: + if (unit->callref) { + char connected_id[32]; + sprintf(connected_id, "%03d%04d", unit->prefix, unit->ident); + call_up_answer(unit->callref, connected_id); + } + unit_new_state(unit, UNIT_GTC_A); + unit->repeat = REPEAT_GTC; + break; + } + PDEBUG_CHAN(DMPT1327, DEBUG_ERROR, "Radio Unit (Prefix:%d Ident:%d) acknowledges, no call\n", unit->prefix, unit->ident); + break; + case MPT_RQQ: /* status from radio */ + unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT2]); + timer_stop(&unit->timer); + PDEBUG_CHAN(DMPT1327, DEBUG_ERROR, "Radio Unit (Prefix:%d Ident:%d) sends RRQ with STATUS=%d\n", unit->prefix, unit->ident, (int)codeword->params[MPT_STATUS]); + switch (codeword->params[MPT_STATUS]) { + case 0x00: + if (unit->state == UNIT_CALLED_ACK) { + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) answers call\n", unit->prefix, unit->ident); + goto answer; + } + break; + case 0x1f: + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) rejects call, releasing\n", unit->prefix, unit->ident); + mpt1327_release(unit); + if (unit->callref) { + call_up_release(unit->callref, CAUSE_NORMAL); + unit->callref = 0; + } + break; + } + break; + case MPT_ALH: /* control channel Aloha for loopback mode */ + case MPT_ALHS: + case MPT_ALHD: + case MPT_ALHE: + case MPT_ALHR: + case MPT_ALHX: + case MPT_ALHF: + /* schedule reception of CCSC word */ + mpt1327->rx_sched.data_num = 1; + mpt1327->rx_sched.data_count = 0; + mpt1327->rx_sched.data_word = MPT_CCSC; + break; + default: + if (mpt1327->sender.loopback) + return; + PDEBUG_CHAN(DMPT1327, DEBUG_NOTICE, "Received unsupported codeword '%s' = '%s' on control channel\n", codeword->short_name, codeword->long_name); + } +} + +void mpt1327_receive_codeword_traffic(mpt1327_t *mpt1327, mpt1327_codeword_t *codeword) +{ + mpt1327_unit_t *unit; + + switch (codeword->type) { + case MPT_MAINT: /* maintenance message */ + unit = get_unit(codeword->params[MPT_PFIX], codeword->params[MPT_IDENT1]); + if (codeword->params[MPT_CHAN] != mpt1327_channel2chan(mpt1327->band, atoi(mpt1327->sender.kanal))) { + PDEBUG_CHAN(DMPT1327, DEBUG_NOTICE, "Radio Unit (Prefix:%d Ident:%d) sends maintenance message on wrong channel %d, ignoring!\n", unit->prefix, unit->ident, (int)codeword->params[MPT_CHAN]); + return; + } + if (!unit->tc) { + PDEBUG_CHAN(DMPT1327, DEBUG_NOTICE, "Radio Unit (Prefix:%d Ident:%d) sends maintenance, but it has no channel assigned, ignoring!\n", unit->prefix, unit->ident); + return; + } + switch (codeword->params[MPT_OPER]) { + case OPER_PRESSEL_ON: + if (sysdef.timeout) + timer_start(&unit->timer, sysdef.timeout); + mpt1327->pressel_on = 1; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) starts transmission\n", unit->prefix, unit->ident); + break; + case OPER_PRESSEL_OFF: + if (sysdef.timeout) + timer_start(&unit->timer, sysdef.timeout); + mpt1327->pressel_on = 0; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) stops transmission\n", unit->prefix, unit->ident); + break; + case OPER_DISCONNECT: + /* ignore while we send clear message */ + if (unit->state == UNIT_CALL_CLEAR) + return; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) disconnects from channel\n", unit->prefix, unit->ident); + if (unit->state == UNIT_CALL) { + timer_stop(&unit->timer); + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Free Traffic Channel %s, because the initiator goes on-hook\n", unit->tc->sender.kanal); + mpt1327_go_idle(unit->tc); + if (unit->callref) { + call_up_release(unit->callref, CAUSE_NORMAL); + unit->callref = 0; + } + } + unit_new_state(unit, UNIT_IDLE); + break; + case OPER_PERIODIC: + if (sysdef.timeout) + timer_start(&unit->timer, sysdef.timeout); + mpt1327->pressel_on = 1; + PDEBUG_CHAN(DMPT1327, DEBUG_INFO, "Radio Unit (Prefix:%d Ident:%d) sends periodic message\n", unit->prefix, unit->ident); + break; + } + break; + default: + if (mpt1327->sender.loopback) + return; + PDEBUG_CHAN(DMPT1327, DEBUG_NOTICE, "Received unsupported codeword '%s' = '%s' on traffic channel\n", codeword->short_name, codeword->long_name); + } +} + +void mpt1327_receive_codeword(mpt1327_t *mpt1327, uint64_t bits, double quality, double level) +{ + mpt1327_codeword_t codeword; + int rc; + + PDEBUG_CHAN(DDSP, DEBUG_INFO, "RX Level: %.0f%% Quality=%.0f%%\n", level * 100.0, quality * 100.0); + + rc = mpt1327_decode_codeword(&codeword, (mpt1327->rx_sched.data_num) ? mpt1327->rx_sched.data_word : -1, (mpt1327->sender.loopback) ? MPT_DOWN : MPT_UP, bits); + if (rc < 0) { + mpt1327->rx_sched.data_num = 0; + mpt1327_reset_sync(mpt1327); /* message complete */ + return; + } + + /* count if we have data words */ + if (mpt1327->rx_sched.data_num) + mpt1327->rx_sched.data_count++; + + switch (mpt1327->dsp_mode) { + case DSP_MODE_CONTROL: + mpt1327_receive_codeword_control(mpt1327, &codeword); + break; + case DSP_MODE_TRAFFIC: + mpt1327_receive_codeword_traffic(mpt1327, &codeword); + break; + default: + ; + } + + /* if all data words are received */ + if (mpt1327->rx_sched.data_num && mpt1327->rx_sched.data_count == mpt1327->rx_sched.data_num) + mpt1327->rx_sched.data_num = 0; + + /* reset receiver unless there is a pending data word to be received */ + if (mpt1327->rx_sched.data_num == 0) + mpt1327_reset_sync(mpt1327); /* message complete */ +} + +void mpt1327_signal_indication(mpt1327_t *mpt1327) +{ + /* restart timer, if enabled */ + if (mpt1327->unit && mpt1327->unit->state == UNIT_CALL) { + if (sysdef.timeout) + timer_start(&mpt1327->unit->timer, sysdef.timeout); + } +} + +/* Timeout handling */ +static void mpt1327_timeout(struct timer *timer) +{ + mpt1327_t *mpt1327 = (mpt1327_t *)timer->priv; + + switch (mpt1327->state) { + default: + break; + } +} + +/* + * call control (from upper layer) + */ + +/* Call control starts call towards mobile station. */ +int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing) +{ + mpt1327_unit_t *unit; + mpt1327_t *tc; + uint8_t prefix; + uint16_t ident; + int i; + + /* 1. check if number is invalid, return INVALNUMBER */ + if (strlen(dialing) != 7) { +inval: + PDEBUG(DMPT1327, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing); + return -CAUSE_INVALNUMBER; + } + for (i = 0; i < 7; i++) { + if (dialing[i] < '0' || dialing[i] > '9') + goto inval; + } + prefix = (dialing[0] - '0') * 100 + (dialing[1] - '0') * 10 + (dialing[2] - '0'); + if (prefix > 127) { + PDEBUG(DMPT1327, DEBUG_NOTICE, "Outgoing call to invalid Prefix '%03d' in number '%s', rejecting! (Prefix must be 000..127)\n", prefix, dialing); + return -CAUSE_INVALNUMBER; + } + ident = atoi(dialing + 3); + if (ident > 8100 || ident < 1) { + PDEBUG(DMPT1327, DEBUG_NOTICE, "Outgoing call to invalid Ident '%04d' in number '%s', rejecting! (Ident must be 0001..8100)\n", ident, dialing); + return -CAUSE_INVALNUMBER; + } + + /* 2. check if given number is already in a call, return BUSY */ + unit = get_unit(prefix, ident); + if (unit->state != UNIT_IDLE) { + PDEBUG(DMPT1327, DEBUG_NOTICE, "Outgoing call to busy Radio Unit, rejecting!\n"); + return -CAUSE_BUSY; + } + + /* 3. check if all channels are busy, return NOCHANNEL */ + tc = search_free_tc(); + if (!tc) { + PDEBUG(DMPT1327, DEBUG_NOTICE, "Outgoing call, but no free channel, rejecting!\n"); + return -CAUSE_NOCHANNEL; + } + + PDEBUG(DMPT1327, DEBUG_INFO, "Outgoing call to Radio Unit (Prefix:%d Ident:%d)\n", unit->prefix, unit->ident); + + /* 4. trying to reach radio unit */ + unit->callref = callref; + mpt1327_new_state(tc, STATE_BUSY, unit); + unit_new_state(unit, UNIT_CALLED_AHY); + unit->repeat = REPEAT_AHY; + unit->called_prefix = unit->prefix; + unit->called_ident = unit->ident; + + return 0; +} + +void call_down_answer(int __attribute__((unused)) callref) +{ +} + +/* Call control sends disconnect (with tones). + * An active call stays active, so tones and annoucements can be received + * by mobile station. + */ +void call_down_disconnect(int callref, int cause) +{ + mpt1327_unit_t *unit; + + PDEBUG(DMPT1327, DEBUG_INFO, "Call has been disconnected by network.\n"); + + unit = find_unit_callref(callref); + if (!unit) { + PDEBUG(DMPT1327, DEBUG_NOTICE, "Outgoing disconnect, but no unit for callref!\n"); + call_up_release(callref, CAUSE_INVALCALLREF); + return; + } + + /* Release when not active */ + if (unit->state == UNIT_CALL) + return; + PDEBUG(DMPT1327, DEBUG_NOTICE, "Outgoing disconnect, but no call, releasing!\n"); + mpt1327_release(unit); + unit->callref = 0; + + call_up_release(callref, cause); +} + +/* Call control releases call toward mobile station. */ +void call_down_release(int callref, __attribute__((unused)) int cause) +{ + mpt1327_unit_t *unit; + + PDEBUG(DMPT1327, DEBUG_INFO, "Call has been released by network, releasing call.\n"); + + unit = find_unit_callref(callref); + if (!unit) { + PDEBUG(DMPT1327, DEBUG_NOTICE, "Outgoing release, but no unit for callref!\n"); + /* don't send release, because caller already released */ + return; + } + + PDEBUG(DMPT1327, DEBUG_NOTICE, "Outgoing release, releasing!\n"); + mpt1327_release(unit); + unit->callref = 0; +} + +/* Receive audio from call instance. */ +void call_down_audio(int callref, sample_t *samples, int count) +{ + mpt1327_unit_t *unit; + + unit = find_unit_callref(callref); + if (!unit) + return; + if (!unit->tc) + return; + + if (unit->tc->state == STATE_BUSY && unit->tc->dsp_mode == DSP_MODE_TRAFFIC) { + sample_t up[(int)((double)count * unit->tc->sender.srstate.factor + 0.5) + 10]; + count = samplerate_upsample(&unit->tc->sender.srstate, samples, count, up); + jitter_save(&unit->tc->sender.dejitter, up, count); + } +} + +void dump_info(void) +{ + dump_units(); +} + +void call_down_clock(void) {} + diff --git a/src/mpt1327/mpt1327.h b/src/mpt1327/mpt1327.h new file mode 100755 index 0000000..7878727 --- /dev/null +++ b/src/mpt1327/mpt1327.h @@ -0,0 +1,155 @@ +#include "../libsquelch/squelch.h" +#include "../libfsk/fsk.h" +#include "../libmobile/sender.h" + +enum mpt1327_band { + BAND_MPT1343_SUB1 = 0, + BAND_MPT1343_SUB2, + BAND_REGIONET43_SUB1, + BAND_REGIONET43_SUB2, +}; + +enum mpt1327_chan_type { + CHAN_TYPE_CC, /* control channel */ + CHAN_TYPE_TC, /* traffic channel */ + CHAN_TYPE_CC_TC, /* combined CC + TC */ +}; + +enum mpt1327_state { + STATE_NULL = 0, + STATE_IDLE, + STATE_BUSY, +}; + +enum dsp_mode { + DSP_MODE_OFF = 0, /* no transmission, no reception */ + DSP_MODE_CONTROL, /* send/receive codewords */ + DSP_MODE_TRAFFIC, /* send/receive codewords and audio */ +}; + +enum mpt1327_sched_state { + /* states on control channel */ + SCHED_STATE_CC_IDLE, + SCHED_STATE_CC_STARTUP, + SCHED_STATE_CC_CCSC, + SCHED_STATE_CC_ADDR, + SCHED_STATE_CC_DATA, + /* states on traffic channel */ + SCHED_STATE_TC_IDLE, + SCHED_STATE_TC_SYNT, + SCHED_STATE_TC_ADDR, + SCHED_STATE_TC_DATA, +}; + +enum mpt1327_called_type { + CALLED_TYPE_UNIT, /* call to radio unit / line unit / group */ + CALLED_TYPE_INTERPFX, /* same as above with different prefix */ + CALLED_TYPE_SYSTEM, /* system wide call */ + CALLED_TYPE_PBX_SHORT, /* call to short PBX extenstion */ + CALLED_TYPE_PBX_LONG, /* call to long PBX extenstion */ + CALLED_TYPE_PSTN_PRE, /* call to PSTN with prearranged number */ + CALLED_TYPE_PSTN_LONG1, /* call to PSTN with 1..9 digits */ + CALLED_TYPE_PSTN_LONG2, /* call to PSTN with 10..31 digits */ +}; + +typedef struct mpt1327_sysdef { + uint16_t sys; /* system idenity */ + int wt; + int per; + int pon; + double timeout; + int framelength; + int bcast_slots; +} mpt1327_sysdef_t; + +typedef struct mpt1327_tx_sched { + enum mpt1327_sched_state state; /* what was currently scheduled */ + int frame_length; /* number of slots in frame */ + int frame_count; /* current slot number */ + int dummy_slot; /* set, if next slot uses a dummy AHY */ + int bcast_count; /* counts slots until sending broadcast */ +} mpt1327_tx_sched_t; + +typedef struct mpt1327_rx_sched { + int data_num; /* set if N data words are awaited */ + int data_count; /* count data words */ + int data_word; /* what data word to parse */ + uint8_t data_prefix; /* unit that requires that data word */ + uint16_t data_ident; +} mpt1327_rx_sched_t; + +struct mpt1327; + +typedef struct mpt1327_unit { + struct mpt1327_unit *next; + uint64_t state; + int repeat; /* number of repeating messages / retries after timeout */ + struct timer timer; /* timeout waiting for unit response */ + struct mpt1327 *tc; /* link to transceiver */ + uint8_t prefix; /* unit's prefix */ + uint16_t ident; /* unit's ident */ + uint8_t called_prefix; + uint16_t called_ident; + enum mpt1327_called_type called_type; + char called_number[33]; /* 0+number+'\0' */ + uint32_t callref; /* PBX/PSTN link to call control */ +} mpt1327_unit_t; + +typedef struct mpt1327 { + sender_t sender; + enum mpt1327_band band; + enum mpt1327_chan_type chan_type; + + /* sender's states */ + enum mpt1327_state state; /* current sender's state */ + struct timer timer; /* inactivity timer to clear channel */ + mpt1327_unit_t *unit; /* link to unit */ + + /* display measurements */ + dispmeasparam_t *dmp_frame_level; + dispmeasparam_t *dmp_frame_quality; + dispmeasparam_t *dmp_super_level; + dispmeasparam_t *dmp_super_quality; + + /* scheduler states */ + mpt1327_tx_sched_t tx_sched; /* downlink scheduler states, see above */ + mpt1327_rx_sched_t rx_sched; /* uplink scheduler states, see above */ + + /* dsp states */ + int repeater; /* in repeater mode the received audio is repeated */ + jitter_t repeater_dejitter; /* forwarding audio */ + int pressel_on; /* set if somebody transmitting on TC */ + enum dsp_mode dsp_mode; /* current mode: audio, durable tone 0 or 1, paging */ + fsk_mod_t fsk_mod; /* fsk processing */ + fsk_demod_t fsk_demod; + uint16_t sync_word; /* current sync word for channel */ + uint16_t rx_sync; /* shift register to detect sync */ + int rx_in_sync; /* if we are in sync and receive bits */ + int rx_mute; /* set, if currently receiving a message */ + uint64_t rx_bits; /* receive frame (one extra byte to terminate string) */ + int rx_count; /* next bit to receive */ + double rx_level[256]; /* level infos */ + double rx_quality[256]; /* quality infos */ + uint64_t tx_bits; /* carries bits of one frame to transmit */ + int tx_bit_num; /* number of bits to tansmit, or 0, if no transmission */ + int tx_count; /* next bit to transmit */ + squelch_t squelch; /* squelch detection process */ +} mpt1327_t; + +void init_sysdef (uint16_t sys, int wt, int per, int pon, int timeout); +void flush_units(void); +double mpt1327_channel2freq(enum mpt1327_band band, int channel, int uplink); +const char *mpt1327_band_name(enum mpt1327_band band); +void mpt1327_band_list(void); +int mpt1327_band_by_short_name(const char *short_name); +void mpt1327_channel_list(void); +int mpt1327_channel_by_short_name(const char *short_name); +const char *chan_type_short_name(enum mpt1327_chan_type chan_type); +const char *chan_type_long_name(enum mpt1327_chan_type chan_type); +int mpt1327_create(enum mpt1327_band band, const char *kanal, enum mpt1327_chan_type chan_type, const char *audiodev, int use_sdr, int samplerate, double rx_gain, double tx_gain, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, double squelch_db); +void mpt1327_check_channels(void); +void mpt1327_destroy(sender_t *sender); +void mpt1327_receive_codeword(mpt1327_t *mpt1327, uint64_t bits, double quality, double level); +int mpt1327_send_codeword(mpt1327_t *mpt1327, uint64_t *bits); +void mpt1327_signal_indication(mpt1327_t *mpt1327); +