dexter has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-pcu/+/31176 )
Change subject: WIP support for ericsson CCU ......................................................................
WIP support for ericsson CCU
Change-Id: I5c0a76667339ca984a12cbd2052f5d9e5b0f9c4d --- M configure.ac M src/Makefile.am A src/ericsson-rbs/er_ccu_descr.h A src/ericsson-rbs/er_ccu_if.c A src/ericsson-rbs/er_ccu_if.h A src/ericsson-rbs/er_ccu_l1_if.c A src/ericsson-rbs/er_ccu_l1_if.h M src/gprs_debug.c M src/gprs_debug.h M src/pcu_main.cpp 10 files changed, 850 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-pcu refs/changes/76/31176/1
diff --git a/configure.ac b/configure.ac index 3f38d93..f78d2be 100644 --- a/configure.ac +++ b/configure.ac @@ -87,6 +87,8 @@ PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.7.0) PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.7.0) PKG_CHECK_MODULES(LIBOSMOGB, libosmogb >= 1.7.0) +PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 1.3.0) +PKG_CHECK_MODULES(LIBOSMOTRAU, libosmotrau >= 1.3.0)
AC_MSG_CHECKING([whether to enable direct DSP access for PDCH of sysmocom-bts]) AC_ARG_ENABLE(sysmocom-dsp, @@ -167,6 +169,14 @@ CPPFLAGS=$oldCPPFLAGS fi
+AC_MSG_CHECKING([whether to enable direct E1 CCU access for PDCH of Ericsson RBS]) +AC_ARG_ENABLE(er-e1-ccu, + AC_HELP_STRING([--enable-er-e1-ccu], + [enable code for Ericsson RBS E1 CCU [default=no]]), + [enable_er_e1_ccu="$enableval"],[enable_er_e1_ccu="no"]) +AC_MSG_RESULT([$enable_er_e1_ccu]) +AM_CONDITIONAL(ENABLE_ER_E1_CCU, test "x$enable_er_e1_ccu" = "xyes") + AC_ARG_ENABLE([vty_tests], AC_HELP_STRING([--enable-vty-tests], [Include the VTY tests in make check [default=no]]), diff --git a/src/Makefile.am b/src/Makefile.am index e020ffa..7c2b995 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,7 +19,7 @@ #
AUTOMAKE_OPTIONS = subdir-objects -AM_CPPFLAGS = -I$(top_srcdir)/include $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGB_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOGSM_CFLAGS) +AM_CPPFLAGS = -I$(top_srcdir)/include $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGB_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOTRAU_CFLAGS)
if ENABLE_SYSMODSP AM_CPPFLAGS += -DENABLE_DIRECT_PHY @@ -33,6 +33,10 @@ AM_CPPFLAGS += -DENABLE_DIRECT_PHY endif
+if ENABLE_ER_E1_CCU +AM_CPPFLAGS += -DENABLE_DIRECT_PHY +endif + AM_CXXFLAGS = -Wall AM_LDFLAGS = -lrt
@@ -207,12 +211,33 @@ osmo-bts-oc2g/oc2gbts.c endif
+if ENABLE_ER_E1_CCU +AM_CPPFLAGS += -I$(srcdir)/ericsson-rbs +AM_CPPFLAGS += -DENABLE_ERICSSON_RBS # RBS but not RBS CCU specific +AM_CPPFLAGS += -DENABLE_ER_E1_CCU # RBS CCU specific + +EXTRA_DIST = \ + ericsson-rbs/er_ccu_l1_if.c \ + ericsson-rbs/er_ccu_l1_if.h \ + ericsson-rbs/er_ccu_if.h + +noinst_HEADERS += \ + ericsson-rbs/er_ccu_l1_if.h \ + ericsson-rbs/er_ccu_if.h + +osmo_pcu_SOURCES += \ + ericsson-rbs/er_ccu_l1_if.c \ + ericsson-rbs/er_ccu_if.c +endif + osmo_pcu_LDADD = \ libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOTRAU_LIBS) \ $(NULL)
#MOSTLYCLEANFILES += testSource testDestination diff --git a/src/ericsson-rbs/er_ccu_descr.h b/src/ericsson-rbs/er_ccu_descr.h new file mode 100644 index 0000000..ae50685 --- /dev/null +++ b/src/ericsson-rbs/er_ccu_descr.h @@ -0,0 +1,39 @@ +#pragma once + +#include <stdint.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/trau/trau_pcu_ericsson.h> + +struct er_ccu_descr; +typedef void (er_ccu_empty) (struct er_ccu_descr * ccu_descr); +typedef void (er_ccu_rx) (struct er_ccu_descr * ccu_descr, const ubit_t *bits, unsigned int num_bits); + +struct er_ccu_descr { + + /* E1-line and timeslot (filled in by user) */ + struct gsm_e1_subslot e1_link; + + /* Callback functions (provided by user) */ + er_ccu_empty *er_ccu_empty_cb; + er_ccu_rx *er_ccu_rx_cb; + + /* I.460 Subslot */ + struct osmo_i460_schan_desc scd; + struct osmo_i460_subchan *schan; + struct osmo_fsm_inst *trau_sync_fi; + bool ccu_connected; + + /* Sync state */ + uint32_t pseq_ccu; + uint32_t pseq_pcu; + uint32_t last_afn_ul; + uint32_t last_afn_dl; + bool ccu_synced; + enum time_adj_val tav; + bool ul_frame_err; + + /* PCU related context */ + uint8_t trx_no; + uint8_t ts; + bool pdch_connected; +}; diff --git a/src/ericsson-rbs/er_ccu_if.c b/src/ericsson-rbs/er_ccu_if.c new file mode 100644 index 0000000..08cc093 --- /dev/null +++ b/src/ericsson-rbs/er_ccu_if.c @@ -0,0 +1,334 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include "er_ccu_if.h" +#include <string.h> +#include <errno.h> + +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/abis.h> +#include <osmocom/trau/trau_sync.h> +#include <osmocom/trau/trau_pcu_ericsson.h> +#include <bts.h> +#include <gprs_debug.h> + +#define E1_TS_BYTES 160 +#define DEBUG_BITS_MAX 1000 +#define DEBUG_BYTES_MAX 40 + +#define LOGPCCU(ccu_descr, level, tag, fmt, args...) \ + LOGP(DE1, level, "E1-%s: (line:%u, TS:%u, SS:%u) " fmt, \ + tag, ccu_descr->e1_link.e1_nr, ccu_descr->e1_link.e1_ts, ccu_descr->e1_link.e1_ts_ss, \ + ## args) + +struct e1_ts_descr { + bool in_use; + struct osmo_i460_timeslot i460_ts; +}; + +struct e1_line_descr { + struct e1_ts_descr e1_ts[NUM_E1_TS - 1]; +}; + +struct e1_line_descr e1_lines[32]; +void *tall_ctx = NULL; + +static const struct e1inp_line_ops dummy_e1_line_ops = { + .sign_link_up = NULL, + .sign_link_down = NULL, + .sign_link = NULL, +}; + +/* called by trau frame synchronizer: feed received MAC blocks into PCU */ +static void sync_frame_out_cb(void *user_data, const ubit_t *bits, unsigned int num_bits) +{ + struct er_ccu_descr *ccu_descr = user_data; + + if (!bits || num_bits == 0) + return; + + LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-RX", "receiving %u TRAU frame bits from subslot (synchronized): %s...\n", + num_bits, osmo_ubit_dump(bits, num_bits > DEBUG_BITS_MAX ? DEBUG_BITS_MAX : num_bits)); + + ccu_descr->er_ccu_rx_cb(ccu_descr, bits, num_bits); +} + +/* called by I.460 de-multeiplexer: feed output of I.460 demux into TRAU frame sync */ +static void e1_i460_demux_bits_cb(struct osmo_i460_subchan *schan, void *user_data, const ubit_t *bits, + unsigned int num_bits) +{ + struct er_ccu_descr *ccu_descr = user_data; + + LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-RX", "receiving %u TRAU frame bits from subslot: %s...\n", num_bits, + osmo_ubit_dump(bits, num_bits > DEBUG_BITS_MAX ? DEBUG_BITS_MAX : num_bits)); + + OSMO_ASSERT(ccu_descr->trau_sync_fi); + osmo_trau_sync_rx_ubits(ccu_descr->trau_sync_fi, bits, num_bits); + +} + +/* called by I.460 de-multeiplexer: ensure that sync indications are sent when mux buffer runs empty */ +static void e1_i460_mux_empty_cb(struct osmo_i460_subchan *schan2, void *user_data) +{ + struct er_ccu_descr *ccu_descr = user_data; + + LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-TX", "demux buffer empty\n"); + ccu_descr->er_ccu_empty_cb(ccu_descr); +} + +/* Function to handle outgoing E1 traffic */ +static void e1_send(struct e1inp_ts *ts) +{ + void *ctx = tall_ctx; + struct e1_ts_descr *ts_descr; + + struct msgb *msg = msgb_alloc_c(ctx, E1_TS_BYTES, "E1-TX-timeslot-bytes"); + uint8_t *ptr; + + /* Note: The line number and ts number that arrives here should be clean. */ + OSMO_ASSERT(ts->line->num < ARRAY_SIZE(e1_lines)); + ts_descr = &e1_lines[ts->line->num].e1_ts[ts->num]; + + /* Get E1 frame from I.460 multiplexer */ + ptr = msgb_put(msg, E1_TS_BYTES); + osmo_i460_mux_out(&ts_descr->i460_ts, ptr, E1_TS_BYTES); + + LOGP(DE1, LOGL_DEBUG, "E1-TX: (line:%u, ts:%u) sending %u bytes: %s...\n", ts->line->num, ts->num, + msgb_length(msg), osmo_hexdump_nospc(msgb_data(msg), + msgb_length(msg) > + DEBUG_BYTES_MAX ? DEBUG_BYTES_MAX : msgb_length(msg))); + + /* Hand data over to the E1 stack */ + msgb_enqueue(&ts->raw.tx_queue, msg); + return; +} + +/* Callback function to handle incoming E1 traffic */ +static void e1_recv_cb(struct e1inp_ts *ts, struct msgb *msg) +{ + struct e1_ts_descr *ts_descr; + + if (msg->len != E1_TS_BYTES) { + LOGP(DE1, LOGL_ERROR, + "E1-RX: (line:%u, ts:%u) receiving bad, expected length is %u, actual length is %u!\n", + ts->line->num, ts->num, E1_TS_BYTES, msg->len); + msgb_free(msg); + return; + } + + LOGP(DE1, LOGL_DEBUG, "E1-RX: (line:%u, ts:%u) receiving %u bytes: %s ...\n", ts->line->num, ts->num, + msg->len, osmo_hexdump_nospc(msg->data, msg->len)); + + /* Note: The line number and ts number that arrives here should be clean. */ + OSMO_ASSERT(ts->line->num < ARRAY_SIZE(e1_lines)); + ts_descr = &e1_lines[ts->line->num].e1_ts[ts->num]; + + /* Hand data over to the I640 demultiplexer. */ + osmo_i460_demux_in(&ts_descr->i460_ts, msg->data, msg->len); + + /* Trigger sending of pending E1 traffic */ + e1_send(ts); + + /* e1inp_rx_ts() does not free() msgb */ + msgb_free(msg); +} + +static struct e1_ts_descr *ts_descr_from_ccu_descr(struct er_ccu_descr *ccu_descr) +{ + /* Make sure E1 line number is valid */ + if (ccu_descr->e1_link.e1_nr >= ARRAY_SIZE(e1_lines)) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "Invalid E1 line number!\n"); + return NULL; + } + + /* Make sure E1 timeslot number is valid */ + if (ccu_descr->e1_link.e1_ts < 1 || ccu_descr->e1_link.e1_ts > NUM_E1_TS - 1) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "Invalid E1 timeslot number!\n"); + return NULL; + } + + /* Timeslots are only initialized once and will stay open after that. */ + return &e1_lines[ccu_descr->e1_link.e1_nr].e1_ts[ccu_descr->e1_link.e1_ts]; +} + +/* Configure an I.460 subslot and add it to the CCU descriptor */ +static int add_i460_subslot(void *ctx, struct er_ccu_descr *ccu_descr) +{ + struct e1_ts_descr *ts_descr; + enum osmo_tray_sync_pat_id sync_pattern; + + /* NOTE: This is a serious error: subslots should be removed when l1if_close_pdch() is called by the PCU. This + * log line points towards a problem with the PDCH management inside the PCU! */ + if (ccu_descr->schan) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "I.460 subslot is already configured -- will not touch it!\n"); + return -EINVAL; + } + + ts_descr = ts_descr_from_ccu_descr(ccu_descr); + if (!ts_descr) + return -EINVAL; + if (!ts_descr->in_use) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "E1 timeslot not ready!\n"); + return -EINVAL; + } + + /* Set up I.460 subchannel and connect it to the MUX on the E1 timeslot */ + if (ccu_descr->e1_link.e1_ts_ss == 255) { + LOGPCCU(ccu_descr, LOGL_INFO, "SETUP", "using 64k subslots\n"); + if (ccu_descr->e1_link.e1_ts_ss > 3) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "Invalid I.460 subslot number!\n"); + return -EINVAL; + } + ccu_descr->scd.rate = OSMO_I460_RATE_64k; + ccu_descr->scd.demux.num_bits = E1_TS_BYTES * 8; + ccu_descr->scd.bit_offset = 0; + sync_pattern = OSMO_TRAU_SYNCP_64_ER_CCU; + } else { + LOGPCCU(ccu_descr, LOGL_INFO, "SETUP", "using 16k subslots\n"); + if (ccu_descr->e1_link.e1_ts_ss != 0) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "Invalid I.460 subslot number!\n"); + return -EINVAL; + } + ccu_descr->scd.rate = OSMO_I460_RATE_16k; + ccu_descr->scd.demux.num_bits = E1_TS_BYTES / 4 * 8; + ccu_descr->scd.bit_offset = ccu_descr->e1_link.e1_ts_ss * 2; + sync_pattern = OSMO_TRAU_SYNCP_16_ER_CCU; + } + + ccu_descr->scd.demux.out_cb_bits = e1_i460_demux_bits_cb; + ccu_descr->scd.demux.out_cb_bytes = NULL; + ccu_descr->scd.demux.user_data = ccu_descr; + ccu_descr->scd.mux.in_cb_queue_empty = e1_i460_mux_empty_cb; + ccu_descr->scd.mux.user_data = ccu_descr; + + LOGPCCU(ccu_descr, LOGL_INFO, "SETUP", "adding I.460 subchannel: bit_offset=%u, num_bits=%lu\n", + ccu_descr->scd.bit_offset, ccu_descr->scd.demux.num_bits); + ccu_descr->schan = osmo_i460_subchan_add(ctx, &ts_descr->i460_ts, &ccu_descr->scd); + if (!ccu_descr->schan) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "adding I.460 subchannel: failed!\n"); + return -EINVAL; + } + + /* Configure TRAU synchronizer */ + ccu_descr->trau_sync_fi = osmo_trau_sync_alloc(NULL, "trau-sync", sync_frame_out_cb, sync_pattern, ccu_descr); + if (!ccu_descr->trau_sync_fi) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "adding I.460 TRAU frame sync: failed!\n"); + } + + return 0; +} + +/* Remove an I.460 subslot from the CCU descriptor */ +static void del_i460_subslot(struct er_ccu_descr *ccu_descr) +{ + if (ccu_descr->schan) + osmo_i460_subchan_del(ccu_descr->schan); + ccu_descr->schan = NULL; + if (ccu_descr->trau_sync_fi) + osmo_fsm_inst_term(ccu_descr->trau_sync_fi, OSMO_FSM_TERM_REGULAR, NULL); + ccu_descr->trau_sync_fi = NULL; + + memset(&ccu_descr->scd, 0, sizeof(ccu_descr->scd)); +} + +/* Configure an E1 timeslot according to the description in the ccu_descr */ +static int open_e1_timeslot(struct er_ccu_descr *ccu_descr) +{ + struct e1inp_line *e1_line; + struct e1_ts_descr *ts_descr; + int rc; + + /* Timeslots are only initialized once and will stay open after that. */ + ts_descr = ts_descr_from_ccu_descr(ccu_descr); + if (!ts_descr) + return -EINVAL; + if (ts_descr->in_use) { + LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP", "E1 timeslot already open -- using it as it is!\n"); + return 0; + } + + /* Find and set up E1 line */ + e1_line = e1inp_line_find(ccu_descr->e1_link.e1_nr); + if (!e1_line) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "no such E1 line!\n"); + return -EINVAL; + } + e1inp_line_bind_ops(e1_line, &dummy_e1_line_ops); + + /* Set up E1 timeslot */ + rc = e1inp_ts_config_raw(&e1_line->ts[ccu_descr->e1_link.e1_ts - 1], e1_line, e1_recv_cb); + if (rc < 0) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "configuration of timeslot failed!\n"); + return -EINVAL; + } + e1inp_line_update(e1_line); + if (rc < 0) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "line update failed!\n"); + return -EINVAL; + } + + /* Make sure the i460 mux is always ready */ + osmo_i460_ts_init(&ts_descr->i460_ts); + + ts_descr->in_use = true; + return 0; +} + +int er_ccu_if_open(struct er_ccu_descr *ccu_descr) +{ + if (open_e1_timeslot(ccu_descr) < 0) + return -EINVAL; + + if (add_i460_subslot(tall_ctx, ccu_descr) < 0) + return -EINVAL; + + ccu_descr->ccu_connected = true; + LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP", "CCU connected.\n"); + return 0; +} + +void er_ccu_if_close(struct er_ccu_descr *ccu_descr) +{ + del_i460_subslot(ccu_descr); + ccu_descr->ccu_connected = false; + LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP", "CCU disconnected.\n"); +} + +void er_ccu_if_tx(struct er_ccu_descr *ccu_descr, const ubit_t *bits, unsigned int num_bits) +{ + struct msgb *msg; + uint8_t *ptr; + msg = msgb_alloc_c(tall_ctx, num_bits, "E1-I.460-PCU-IND-frame"); + ptr = msgb_put(msg, num_bits); + memcpy(ptr, bits, num_bits); + LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-TX", "sending %u bits: %s...\n", msgb_length(msg), + osmo_ubit_dump(msgb_data(msg), msgb_length(msg) > DEBUG_BITS_MAX ? DEBUG_BITS_MAX : msgb_length(msg))); + osmo_i460_mux_enqueue(ccu_descr->schan, msg); +} + +void er_ccu_if_init(void *ctx) +{ + libosmo_abis_init(ctx); + e1inp_vty_init(); + + tall_ctx = ctx; + memset(e1_lines, 0, sizeof(e1_lines)); +} diff --git a/src/ericsson-rbs/er_ccu_if.h b/src/ericsson-rbs/er_ccu_if.h new file mode 100644 index 0000000..0c92072 --- /dev/null +++ b/src/ericsson-rbs/er_ccu_if.h @@ -0,0 +1,10 @@ +#pragma once + +#include <stdint.h> +#include <osmocom/abis/e1_input.h> +#include "er_ccu_descr.h" + +int er_ccu_if_open(struct er_ccu_descr *ccu_descr); +void er_ccu_if_close(struct er_ccu_descr *ccu_descr); +void er_ccu_if_tx(struct er_ccu_descr *ccu_descr, const ubit_t * bits, unsigned int num_bits); +void er_ccu_if_init(void *ctx); diff --git a/src/ericsson-rbs/er_ccu_l1_if.c b/src/ericsson-rbs/er_ccu_l1_if.c new file mode 100644 index 0000000..87e8c5b --- /dev/null +++ b/src/ericsson-rbs/er_ccu_l1_if.c @@ -0,0 +1,415 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH info@sysmocom.de + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include "er_ccu_descr.h" +#include "er_ccu_l1_if.h" +#include "er_ccu_if.h" + +#include <string.h> +#include <errno.h> +#include <pcu_l1_if.h> + +#include <osmocom/pcu/pcuif_proto.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/abis.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/trau/trau_sync.h> +#include <osmocom/trau/trau_pcu_ericsson.h> +#include <osmocom/gsm/gsm0502.h> +#include <osmocom/core/talloc.h> + +#include <bts.h> + +void *tall_ctx; + +const uint8_t fn_inc_table[4] = { 4, 4, 5, 0 }; +const uint8_t blk_nr_table[4] = { 4, 4, 5, 0 }; + +#define SYNC_CHECK_INTERVAL 2000 + +/* Subtrahend to convert Ericsson adjusted (block ending) fn to regular fn (uplink only) */ +#define AFN_SUBTRAHEND 3 + +#define LOGPL1IF(ccu_descr, level, tag, fmt, args...) \ + LOGP(DL1IF, level, "%s: (E1-line:%u, E1-TS:%u, E1-SS:%u, PDCH-TS:%u, TRX:%u) " fmt, \ + tag, ccu_descr->e1_link.e1_nr, ccu_descr->e1_link.e1_ts, ccu_descr->e1_link.e1_ts_ss, \ + ccu_descr->ts, ccu_descr->trx_no, \ + ## args) + +/* Calculate GPRS block number from frame number */ +static uint8_t fn_to_block_nr(uint32_t fn) +{ + /* Note: See also 3GPP TS 03.64 6.5.7.2.1, + * Mapping on the multiframe structure */ + + uint8_t rel_fn; + uint8_t super_block; + uint8_t local_block; + + rel_fn = fn % 52; + + /* Warn in case of frames that do not belong to a block */ + if (rel_fn == 12 || rel_fn == 25 || rel_fn == 38 || rel_fn == 51) + LOGP(DL1IF, LOGL_ERROR, "Frame number is referencing invalid block!\n"); + + super_block = (rel_fn / 13); + local_block = rel_fn % 13 / 4; + return super_block * 3 + local_block; +} + +static uint32_t fn_dl_advance(uint32_t fn, uint32_t n_blocks) +{ + uint32_t i; + + uint8_t inc_fn; + + for (i = 0; i < n_blocks; i++) { + inc_fn = fn_inc_table[(fn % 13) / 4]; + fn = GSM_TDMA_FN_SUM(fn, inc_fn); + } + + return fn; +} + +/* Receive block from CCU */ +static void er_ccu_rx_cb(struct er_ccu_descr *ccu_descr, const ubit_t *bits, unsigned int num_bits) +{ + int rc; + struct er_gprs_trau_frame trau_frame; + uint8_t inc_ul; + uint8_t inc_dl; + uint32_t afn_ul; + uint32_t afn_dl; + uint32_t afn_ul_comp; + uint32_t afn_dl_comp; + struct pcu_l1_meas meas = { 0 }; + struct gprs_rlcmac_bts *bts; + struct gprs_rlcmac_pdch *pdch; + + /* Compute the current frame numbers from the last frame number */ + inc_ul = fn_inc_table[(ccu_descr->last_afn_ul % 13) / 4]; + inc_dl = fn_inc_table[(ccu_descr->last_afn_dl % 13) / 4]; + afn_ul = GSM_TDMA_FN_SUM(ccu_descr->last_afn_ul, inc_ul); + afn_dl = GSM_TDMA_FN_SUM(ccu_descr->last_afn_dl, inc_dl); + + /* Compute compensated frame numbers. This will be the framenumbers we + * will use to exchange blocks with the PCU code. The following applies: + * + * 1. The uplink related frame numbers sent by the ericsson CCU refer + * to the end of a block. This is compensated by subtracting three + * frames. + * 2. The CCU downlink frame number runs one block past the uplink + * frame number. This needs to be compesated as well (+1). + * 3. The difference between the local (PCU) and the returned (CCU) + * pseq counter value is the number of blocks that the PCU must + * shift its downlink alignment in order to compensate the link + * latency between PCU and CCU. */ + afn_ul_comp = GSM_TDMA_FN_SUB(afn_ul, AFN_SUBTRAHEND); //<== prooven correct! + afn_dl_comp = afn_dl; + afn_dl_comp = fn_dl_advance(afn_dl_comp, GSM_TDMA_FN_DIFF(ccu_descr->pseq_pcu, ccu_descr->pseq_ccu) + 1); + + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-SYNC", + "afn_ul=%u/%u, afn_dl=%u/%u, afn_diff=%u => afn_ul_comp=%u/%u, afn_dl_comp=%u/%u, afn_diff_comp=%u\n", + afn_ul, afn_ul % 52, afn_dl, afn_dl % 52, GSM_TDMA_FN_DIFF(afn_ul, afn_dl), afn_ul_comp, + afn_ul_comp % 52, afn_dl_comp, afn_dl_comp % 52, GSM_TDMA_FN_DIFF(afn_ul_comp, afn_dl_comp)); + + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-SYNC", "pseq_pcu=%u, pseq_ccu=%u, pseq_diff=%u\n", + ccu_descr->pseq_pcu, ccu_descr->pseq_ccu, GSM_TDMA_FN_DIFF(ccu_descr->pseq_pcu, ccu_descr->pseq_ccu)); + + /* Decode indication from CCU */ + if (ccu_descr->e1_link.e1_ts_ss == 255) + rc = er_gprs_trau_frame_decode_64k(&trau_frame, bits); + else + rc = er_gprs_trau_frame_decode_16k(&trau_frame, bits); + if (rc < 0) { + LOGPL1IF(ccu_descr, LOGL_ERROR, "CCU-XXXX-IND", + "unable to decode uplink TRAU frame, afn_ul_comp=%u/%u\n", afn_ul_comp, afn_ul_comp % 52); + + /* Report to the CCU that there is an issue with downlink TRAU frames, the CCU will then send + * a CCU-SYNC-IND within the next TRAU frame, so we can check if we are still in sync and trigger + * synchronization procedure if necessary. */ + ccu_descr->ul_frame_err = true; + goto skip; + } + + switch (trau_frame.type) { + case ER_GPRS_TRAU_FT_SYNC: + if (trau_frame.u.ccu_sync_ind.pseq != 0x3FFFFF) { + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-SYNC-IND", + "tav=%u, dbe=%u, dfe=%u, pseq=%u, afn_ul=%u, afn_dl=%u\n", + trau_frame.u.ccu_sync_ind.tav, trau_frame.u.ccu_sync_ind.dbe, + trau_frame.u.ccu_sync_ind.dfe, trau_frame.u.ccu_sync_ind.pseq, + trau_frame.u.ccu_sync_ind.afn_ul, trau_frame.u.ccu_sync_ind.afn_dl); + + /* Synchronize the current CCU PSEQ state */ + ccu_descr->pseq_ccu = trau_frame.u.ccu_sync_ind.pseq; + } else { + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-SYNC-IND", + "tav=%u, dbe=%u, dfe=%u, pseq=(none), afn_ul=%u, afn_dl=%u\n", + trau_frame.u.ccu_sync_ind.tav, trau_frame.u.ccu_sync_ind.dbe, + trau_frame.u.ccu_sync_ind.dfe, trau_frame.u.ccu_sync_ind.afn_ul, + trau_frame.u.ccu_sync_ind.afn_dl); + } + + ccu_descr->tav = trau_frame.u.ccu_sync_ind.tav; + + /* Check if we are in sync with the CCU, if not trigger synchronization procedure */ + if (afn_ul != trau_frame.u.ccu_sync_ind.afn_ul || afn_dl != trau_frame.u.ccu_sync_ind.afn_dl) { + if (afn_ul != trau_frame.u.ccu_sync_ind.afn_ul) + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-SYNC-IND", + "afn_ul=%u (computed) != afn_ul=%u (sync-ind) => delta=%u \n", afn_ul, + trau_frame.u.ccu_sync_ind.afn_ul, GSM_TDMA_FN_DIFF(afn_ul, + trau_frame.u.ccu_sync_ind. + afn_ul)); + if (afn_dl != trau_frame.u.ccu_sync_ind.afn_dl) + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-SYNC-IND", + "afn_dl=%u (computed) != afn_dl=%u (sync-ind) => delta=%u \n", afn_dl, + trau_frame.u.ccu_sync_ind.afn_dl, GSM_TDMA_FN_DIFF(afn_dl, + trau_frame.u.ccu_sync_ind. + afn_dl)); + LOGPL1IF(ccu_descr, LOGL_NOTICE, "CCU-SYNC-IND", + "FN jump detected, lost sync with CCU -- (re)synchronizing...\n"); + ccu_descr->ccu_synced = false; + } else { + LOGPL1IF(ccu_descr, LOGL_NOTICE, "CCU-SYNC-IND", "in sync with CCU\n"); + ccu_descr->ccu_synced = true; + } + + /* Overwrite calculated afn_ul and afn_dl with the actual values from the SYNC indication */ + afn_ul = trau_frame.u.ccu_sync_ind.afn_ul; + afn_dl = trau_frame.u.ccu_sync_ind.afn_dl; + + break; + case ER_GPRS_TRAU_FT_DATA: + + ccu_descr->tav = trau_frame.u.ccu_data_ind.tav; + + /* Ignore all data indications that contain only noise */ + if (!trau_frame.u.ccu_data_ind.u.gprs.parity_ok) + break; + + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-DATA-IND", + "tav=%u, dbe=%u, cs_hdr=%u, rx_lev=%u, est_acc_del_dev=%u," + "block_qual=%u, parity_ok=%u, data=%s<==, afn_ul_comp=%u/%u\n", trau_frame.u.ccu_data_ind.tav, + trau_frame.u.ccu_data_ind.dbe, trau_frame.u.ccu_data_ind.cs_hdr, + trau_frame.u.ccu_data_ind.rx_lev, trau_frame.u.ccu_data_ind.est_acc_del_dev, + trau_frame.u.ccu_data_ind.u.gprs.block_qual, trau_frame.u.ccu_data_ind.u.gprs.parity_ok, + osmo_hexdump_nospc(trau_frame.u.ccu_data_ind.data, trau_frame.u.ccu_data_ind.data_len), + afn_ul_comp, afn_ul_comp % 52); + + /* Hand received MAC block into PCU */ + bts = llist_first_entry_or_null(&the_pcu->bts_list, struct gprs_rlcmac_bts, list); + if (!bts) + break; + meas.have_rssi = 1; + meas.rssi = rxlev2dbm(trau_frame.u.ccu_data_ind.rx_lev); + meas.have_link_qual = 1; + meas.link_qual = trau_frame.u.ccu_data_ind.u.gprs.block_qual; + pdch = &bts->trx[ccu_descr->trx_no].pdch[ccu_descr->ts]; + rc = pcu_rx_data_ind_pdtch(bts, pdch, trau_frame.u.ccu_data_ind.data, + trau_frame.u.ccu_data_ind.data_len, afn_ul_comp, &meas); + break; + default: + LOGPL1IF(ccu_descr, LOGL_ERROR, "CCU-XXXX-IND", "unhandled CCU indication!\n"); + } + +skip: + if (ccu_descr->ccu_synced) { + bts = llist_first_entry_or_null(&the_pcu->bts_list, struct gprs_rlcmac_bts, list); + if (bts) { + /* The PCU timing is locked to the uplink fame number. The downlink frame number is advanced + * into the future so that the line latency is compensated and the frame arrives at the right + * point in time. */ + pdch = &bts->trx[ccu_descr->trx_no].pdch[ccu_descr->ts]; + pcu_rx_block_time(bts, pdch->trx->arfcn, afn_ul_comp, ccu_descr->ts); + rc = pcu_rx_rts_req_pdtch(bts, ccu_descr->trx_no, ccu_descr->ts, afn_dl_comp, + fn_to_block_nr(afn_dl_comp)); + } + } + + /* We do not receive sync indications in every cycle. When traffic is transfered we won't get frame numbers + * from the CCU. In this case we must update the last_afn_ul/dl values from the computed frame numbers + * (see above) */ + ccu_descr->last_afn_ul = afn_ul; + ccu_descr->last_afn_dl = afn_dl; + ccu_descr->pseq_pcu++; + ccu_descr->pseq_ccu++; +} + +static void er_ccu_empty_cb(struct er_ccu_descr *ccu_descr) +{ + struct er_gprs_trau_frame trau_frame; + ubit_t trau_frame_encoded[ER_GPRS_TRAU_FRAME_LEN_64K]; + int rc; + + memset(&trau_frame, 0, sizeof(trau_frame)); + trau_frame.u.pcu_sync_ind.pseq = ccu_descr->pseq_pcu; + trau_frame.u.pcu_sync_ind.tav = ccu_descr->tav; + trau_frame.u.pcu_sync_ind.fn_ul = 0x3FFFFF; + trau_frame.u.pcu_sync_ind.fn_dl = 0x3FFFFF; + trau_frame.u.pcu_sync_ind.fn_ss = 0x3FFFFF; + trau_frame.u.pcu_sync_ind.ls = 0x3FFFFF; + trau_frame.u.pcu_sync_ind.ss = 0x3FFFFF; + trau_frame.type = ER_GPRS_TRAU_FT_SYNC; + + if (ccu_descr->e1_link.e1_ts_ss == 255) + rc = er_gprs_trau_frame_encode_64k(trau_frame_encoded, &trau_frame); + else + rc = er_gprs_trau_frame_encode_16k(trau_frame_encoded, &trau_frame); + if (rc < 0) { + LOGPL1IF(ccu_descr, LOGL_ERROR, "PCU-SYNC-IND", "unable to encode TRAU frame\n"); + return; + } + LOGPL1IF(ccu_descr, LOGL_DEBUG, "PCU-SYNC-IND", "pseq=%u, tav=%u\n", + trau_frame.u.pcu_sync_ind.pseq, trau_frame.u.pcu_sync_ind.tav); + er_ccu_if_tx(ccu_descr, trau_frame_encoded, rc); + + /* Make sure timing adjustment value is reset after use */ + ccu_descr->tav = TIME_ADJ_NONE; +} + +/* use the length of the block to determine the coding scheme */ +static int cs_from_len(uint8_t len) +{ + switch (len) { + case 23: + return CS_OR_HDR_CS1; + case 34: + return CS_OR_HDR_CS2; + case 40: + return CS_OR_HDR_CS3; + case 54: + return CS_OR_HDR_CS4; + default: + return -EINVAL; + } +} + +/* send packet data request to L1 */ +int l1if_pdch_req(void *obj, uint8_t ts, int is_ptcch, uint32_t fn, + uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len) +{ + struct er_ccu_descr *ccu_descr = obj; + struct er_gprs_trau_frame trau_frame; + ubit_t trau_frame_encoded[ER_GPRS_TRAU_FRAME_LEN_64K]; + int rc; + + /* Make sure that the CCU is synchronized and connected. */ + if (!ccu_descr->ccu_connected) { + LOGPL1IF(ccu_descr, LOGL_NOTICE, "PCU-DATA-IND", "CCU not connected, tossing MAC block...\n"); + return -EINVAL; + } + if (!ccu_descr->ccu_synced) { + LOGPL1IF(ccu_descr, LOGL_NOTICE, "PCU-DATA-IND", "CCU not synchronized, tossing MAC block...\n"); + return -EINVAL; + } + + memset(&trau_frame, 0, sizeof(trau_frame)); + trau_frame.type = ER_GPRS_TRAU_FT_DATA; + + rc = cs_from_len(len); + if (rc < 0) { + LOGPL1IF(ccu_descr, LOGL_ERROR, "PCU-DATA-IND", + "unable to encode TRAU frame, invalid CS or MCS value set\n"); + return -EINVAL; + } + trau_frame.u.pcu_data_ind.cs_hdr = (enum er_cs_or_hdr)rc; + trau_frame.u.pcu_data_ind.tav = ccu_descr->tav; + trau_frame.u.pcu_data_ind.ul_frame_err = ccu_descr->ul_frame_err; + trau_frame.u.pcu_data_ind.ul_chan_mode = ER_UL_CHMOD_NB_GMSK; /* <- TODO: select this depending on the CS (EGPRS) */ + OSMO_ASSERT(len < sizeof(trau_frame.u.pcu_data_ind.data)); + memcpy(trau_frame.u.pcu_data_ind.data, data, len); + + /* Regulary ignore one MAC block in uplink. The CCU will then send one CCU-SYNC-IND instead. We use this + * indication to check whether we are still in sync with the CCU. */ + if (fn % SYNC_CHECK_INTERVAL == 0) + trau_frame.u.pcu_data_ind.ul_chan_mode = ER_UL_CHMOD_VOID; + + if (ccu_descr->e1_link.e1_ts_ss == 255) + rc = er_gprs_trau_frame_encode_64k(trau_frame_encoded, &trau_frame); + else + rc = er_gprs_trau_frame_encode_16k(trau_frame_encoded, &trau_frame); + if (rc < 0) { + LOGPL1IF(ccu_descr, LOGL_ERROR, "PCU-DATA-IND", "unable to encode TRAU frame\n"); + return -EINVAL; + } + LOGPL1IF(ccu_descr, LOGL_DEBUG, "PCU-DATA-IND", + "tav=%u, ul_frame_err=%u, cs_hdr=%u, ul_chan_mode=%u, atten_db=%u, timing_offset=%u," + " data=%s==>, fn=%u/%u (comp)\n", trau_frame.u.pcu_data_ind.tav, + trau_frame.u.pcu_data_ind.ul_frame_err, trau_frame.u.pcu_data_ind.cs_hdr, + trau_frame.u.pcu_data_ind.ul_chan_mode, trau_frame.u.pcu_data_ind.atten_db, + trau_frame.u.pcu_data_ind.timing_offset, osmo_hexdump_nospc(trau_frame.u.pcu_data_ind.data, len), fn, + fn % 52); + er_ccu_if_tx(ccu_descr, trau_frame_encoded, rc); + + /* Make sure timing adjustment value is reset after use */ + ccu_descr->tav = TIME_ADJ_NONE; + ccu_descr->ul_frame_err = false; + + return 0; +} + +void *l1if_open_pdch(uint8_t trx_no, uint32_t hlayer1) +{ + struct er_ccu_descr *ccu_descr; + + /* Note: We do not have enough information to really open anything at + * this point. We will just create the CCU context. */ + + ccu_descr = talloc_zero(tall_ctx, struct er_ccu_descr); + OSMO_ASSERT(ccu_descr); + ccu_descr->er_ccu_rx_cb = er_ccu_rx_cb; + ccu_descr->er_ccu_empty_cb = er_ccu_empty_cb; + ccu_descr->trx_no = trx_no; + + return ccu_descr; +} + +int l1if_close_pdch(void *obj) +{ + struct er_ccu_descr *ccu_descr = obj; + er_ccu_if_close(ccu_descr); + talloc_free(ccu_descr); + return 0; +} + +int l1if_connect_pdch(void *obj, uint8_t ts) +{ + struct er_ccu_descr *ccu_descr = obj; + int rc; + + ccu_descr->ts = ts; + + rc = pcu_l1if_get_ccu_conn_pars(&ccu_descr->e1_link, ccu_descr->ts, ccu_descr->trx_no); + if (rc < 0) + return -EINVAL; + + rc = er_ccu_if_open(ccu_descr); + if (rc < 0) + return -EINVAL; + + return 0; +} + +void er_ccu_init(void *ctx) +{ + er_ccu_if_init(ctx); +} diff --git a/src/ericsson-rbs/er_ccu_l1_if.h b/src/ericsson-rbs/er_ccu_l1_if.h new file mode 100644 index 0000000..a4599f0 --- /dev/null +++ b/src/ericsson-rbs/er_ccu_l1_if.h @@ -0,0 +1,3 @@ +#pragma once + +void er_ccu_init(void *ctx); diff --git a/src/gprs_debug.c b/src/gprs_debug.c index 03ef083..8aeca5b 100644 --- a/src/gprs_debug.c +++ b/src/gprs_debug.c @@ -128,6 +128,13 @@ .loglevel = LOGL_NOTICE, .enabled = 1, }, + [DE1] = { + .name = "DE1", + .color = "\033[1;31m", + .description = "E1 line handling", + .loglevel = LOGL_NOTICE, + .enabled = 1, + }, };
static int filter_fn(const struct log_context *ctx, diff --git a/src/gprs_debug.h b/src/gprs_debug.h index 320c739..db9630c 100644 --- a/src/gprs_debug.h +++ b/src/gprs_debug.h @@ -41,6 +41,7 @@ DPCU, DNACC, DRIM, + DE1, aDebug_LastEntry };
diff --git a/src/pcu_main.cpp b/src/pcu_main.cpp index 901ee6c..5738be5 100644 --- a/src/pcu_main.cpp +++ b/src/pcu_main.cpp @@ -48,6 +48,7 @@ #include <osmocom/core/stats.h> #include <osmocom/core/gsmtap.h> #include <osmocom/core/gsmtap_util.h> +#include "ericsson-rbs/er_ccu_l1_if.h" }
extern struct gprs_nsvc *nsvc; @@ -250,6 +251,10 @@ osmo_cpu_sched_vty_init(tall_pcu_ctx); logging_vty_add_deprecated_subsys(tall_pcu_ctx, "bssgp");
+#ifdef ENABLE_ER_E1_CCU + er_ccu_init(tall_pcu_ctx); +#endif + handle_options(argc, argv); if ((!!spoof_mcc) + (!!spoof_mnc) == 1) { fprintf(stderr, "--mcc and --mnc must be specified "