aboutsummaryrefslogtreecommitdiffstats
path: root/src/ericsson-rbs
diff options
context:
space:
mode:
Diffstat (limited to 'src/ericsson-rbs')
-rw-r--r--src/ericsson-rbs/er_ccu_descr.h53
-rw-r--r--src/ericsson-rbs/er_ccu_if.c416
-rw-r--r--src/ericsson-rbs/er_ccu_if.h10
-rw-r--r--src/ericsson-rbs/er_ccu_l1_if.c543
4 files changed, 1022 insertions, 0 deletions
diff --git a/src/ericsson-rbs/er_ccu_descr.h b/src/ericsson-rbs/er_ccu_descr.h
new file mode 100644
index 00000000..9fe1aab8
--- /dev/null
+++ b/src/ericsson-rbs/er_ccu_descr.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/trau/trau_pcu_ericsson.h>
+
+struct er_ccu_descr;
+struct e1_conn_pars;
+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 e1_conn_pars *e1_conn_pars;
+
+ /* Callback functions (provided by user) */
+ er_ccu_empty *er_ccu_empty_cb;
+ er_ccu_rx *er_ccu_rx_cb;
+
+ /* I.460 Subslot */
+ struct {
+ struct osmo_i460_schan_desc scd;
+ struct osmo_i460_subchan *schan;
+ struct osmo_fsm_inst *trau_sync_fi;
+ bool ccu_connected;
+ } link;
+
+ /* TRAU Sync state */
+ struct {
+ uint32_t pseq_ccu; /* CCU sequence counter (remote) */
+ uint32_t pseq_pcu; /* PCU sequence counter (local) */
+ uint32_t last_afn_ul; /* Adjusted frame number, uplink */
+ uint32_t last_afn_dl; /* Adjusted frame number, downlink */
+ enum time_adj_val tav; /* Last time adjustment value */
+ bool ul_frame_err; /* True when last uplink TRAU frame was bad */
+ bool ccu_synced; /* True when PCU is in sync with CCU */
+ } sync;
+
+ /* PCU related context */
+ struct {
+ uint8_t trx_no;
+ uint8_t bts_nr;
+ uint8_t ts;
+ } pcu;
+
+
+};
+
+struct er_trx_descr {
+ struct er_ccu_descr ts_ccu_descr[8];
+};
diff --git a/src/ericsson-rbs/er_ccu_if.c b/src/ericsson-rbs/er_ccu_if.c
new file mode 100644
index 00000000..98abbf7d
--- /dev/null
+++ b/src/ericsson-rbs/er_ccu_if.c
@@ -0,0 +1,416 @@
+/*
+ * (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 General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <er_ccu_if.h>
+#include <er_ccu_descr.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>
+#include <pcu_l1_if.h>
+
+#define E1_TS_BYTES 160
+#define DEBUG_BITS_MAX 1280
+#define DEBUG_BYTES_MAX 40
+
+#define LOGPCCU(ccu_descr, level, tag, fmt, args...) \
+ LOGP(DE1, level, "E1TS(%u:%u:%u) %s:" fmt, \
+ ccu_descr->e1_conn_pars->e1_nr, ccu_descr->e1_conn_pars->e1_ts, \
+ ccu_descr->e1_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL ? 0 : ccu_descr->e1_conn_pars->e1_ts_ss, tag, \
+ ## args)
+
+struct e1_ts_descr {
+ uint8_t usecount;
+ bool i460_ts_initialized;
+ struct osmo_i460_timeslot i460_ts;
+};
+
+struct e1_line_descr {
+ struct e1_ts_descr e1_ts[NUM_E1_TS - 1];
+};
+
+static struct e1_line_descr e1_lines[32];
+static void *tall_ccu_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-multiplexer: 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->link.trau_sync_fi);
+ osmo_trau_sync_rx_ubits(ccu_descr->link.trau_sync_fi, bits, num_bits);
+
+}
+
+/* called by I.460 de-multiplexer: 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);
+}
+
+/* handle outgoing E1 traffic */
+static void e1_send_ts_frame(struct e1inp_ts *ts)
+{
+ void *ctx = tall_ccu_ctx;
+ struct e1_ts_descr *ts_descr;
+ struct msgb *msg;
+ uint8_t *ptr;
+
+ /* 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];
+
+ /* Do not send anything in case the E1 timeslot is not ready. */
+ if (ts_descr->usecount == 0)
+ return;
+
+ /* Get E1 frame from I.460 multiplexer */
+ msg = msgb_alloc_c(ctx, E1_TS_BYTES, "E1-TX-timeslot-bytes");
+ ptr = msgb_put(msg, E1_TS_BYTES);
+ osmo_i460_mux_out(&ts_descr->i460_ts, ptr, E1_TS_BYTES);
+
+ LOGPITS(ts, DE1, LOGL_DEBUG, "E1-TX: sending %u bytes: %s...\n",
+ 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);
+}
+
+/* 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) {
+ LOGPITS(ts, DE1, LOGL_ERROR,
+ "E1-RX: receiving bad, expected length is %u, actual length is %u!\n",
+ E1_TS_BYTES, msg->len);
+ msgb_free(msg);
+ return;
+ }
+
+ LOGPITS(ts, DE1, LOGL_DEBUG, "E1-RX: receiving %u bytes: %s ...\n",
+ 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_frame(ts);
+
+ /* e1inp_rx_ts(), the caller of this callback 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_conn_pars->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_conn_pars->e1_ts < 1 || ccu_descr->e1_conn_pars->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_conn_pars->e1_nr].e1_ts[ccu_descr->e1_conn_pars->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;
+
+ if (ccu_descr->link.schan) {
+ /* NOTE: This is a serious error: subslots should be removed when l1if_close_trx() is called by the
+ * PCU. This log line points towards a problem with the PDCH management inside the PCU! */
+ 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->usecount == 0) {
+ 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_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL) {
+ LOGPCCU(ccu_descr, LOGL_INFO, "SETUP", "using 64k subslots\n");
+ ccu_descr->link.scd.rate = OSMO_I460_RATE_64k;
+ ccu_descr->link.scd.demux.num_bits = E1_TS_BYTES * 8;
+ ccu_descr->link.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_conn_pars->e1_ts_ss > 3) {
+ LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "Invalid I.460 subslot number!\n");
+ return -EINVAL;
+ }
+ ccu_descr->link.scd.rate = OSMO_I460_RATE_16k;
+ ccu_descr->link.scd.demux.num_bits = E1_TS_BYTES / 4 * 8;
+ ccu_descr->link.scd.bit_offset = ccu_descr->e1_conn_pars->e1_ts_ss * 2;
+ sync_pattern = OSMO_TRAU_SYNCP_16_ER_CCU;
+ }
+
+ ccu_descr->link.scd.demux.out_cb_bits = e1_i460_demux_bits_cb;
+ ccu_descr->link.scd.demux.out_cb_bytes = NULL;
+ ccu_descr->link.scd.demux.user_data = ccu_descr;
+ ccu_descr->link.scd.mux.in_cb_queue_empty = e1_i460_mux_empty_cb;
+ ccu_descr->link.scd.mux.user_data = ccu_descr;
+
+ LOGPCCU(ccu_descr, LOGL_INFO, "SETUP", "adding I.460 subchannel: bit_offset=%u, num_bits=%zu\n",
+ ccu_descr->link.scd.bit_offset, ccu_descr->link.scd.demux.num_bits);
+ ccu_descr->link.schan = osmo_i460_subchan_add(ctx, &ts_descr->i460_ts, &ccu_descr->link.scd);
+ if (!ccu_descr->link.schan) {
+ LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "adding I.460 subchannel: failed!\n");
+ return -EINVAL;
+ }
+
+ /* Configure TRAU synchronizer */
+ ccu_descr->link.trau_sync_fi = osmo_trau_sync_alloc(tall_ccu_ctx, "trau-sync", sync_frame_out_cb, sync_pattern, ccu_descr);
+ if (!ccu_descr->link.trau_sync_fi) {
+ LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "adding I.460 TRAU frame sync: failed!\n");
+ return -EINVAL;
+ }
+
+ /* Ericsson uses a different synchronization pattern for MCS9 TRAU frames */
+ if (sync_pattern == OSMO_TRAU_SYNCP_64_ER_CCU)
+ osmo_trau_sync_set_secondary_pat(ccu_descr->link.trau_sync_fi, OSMO_TRAU_SYNCP_64_ER_CCU_MCS9, 1);
+
+ 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->link.schan)
+ osmo_i460_subchan_del(ccu_descr->link.schan);
+ ccu_descr->link.schan = NULL;
+ if (ccu_descr->link.trau_sync_fi)
+ osmo_fsm_inst_term(ccu_descr->link.trau_sync_fi, OSMO_FSM_TERM_REGULAR, NULL);
+ ccu_descr->link.trau_sync_fi = NULL;
+
+ memset(&ccu_descr->link.scd, 0, sizeof(ccu_descr->link.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;
+
+ /* Find timeslot descriptor and check if the timeslot is already open. */
+ ts_descr = ts_descr_from_ccu_descr(ccu_descr);
+ if (!ts_descr)
+ return -EINVAL;
+ if (ts_descr->usecount > 0) {
+ LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP", "E1 timeslot already open -- using it as it is!\n");
+ ts_descr->usecount++;
+ return 0;
+ }
+
+ /* Find and set up E1 line */
+ e1_line = e1inp_line_find(ccu_descr->e1_conn_pars->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_conn_pars->e1_ts - 1], e1_line, e1_recv_cb);
+ if (rc < 0) {
+ LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "configuration of timeslot failed!\n");
+ return -EINVAL;
+ }
+ rc = 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 ready */
+ if (!ts_descr->i460_ts_initialized) {
+ osmo_i460_ts_init(&ts_descr->i460_ts);
+ ts_descr->i460_ts_initialized = true;
+ }
+
+ ts_descr->usecount++;
+ OSMO_ASSERT(ts_descr->usecount == 1);
+
+ return 0;
+}
+
+/* Configure an E1 timeslot according to the description in the ccu_descr */
+static int close_e1_timeslot(struct er_ccu_descr *ccu_descr)
+{
+ struct e1inp_line *e1_line;
+ struct e1_ts_descr *ts_descr;
+ int rc;
+
+ /* Find timeslot descriptor and check if the timeslot is still used by another subslot. */
+ ts_descr = ts_descr_from_ccu_descr(ccu_descr);
+ if (!ts_descr)
+ return -EINVAL;
+ if (ts_descr->usecount > 1) {
+ LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP",
+ "E1 timeslot still in used by another subslot, leaving it open!\n");
+ ts_descr->usecount--;
+ return 0;
+ } else if (ts_descr->usecount == 0) {
+ /* This should not be as it means we close the timeslot too often. */
+ LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "E1 timeslot already closed, leaving it as it is...\n");
+ return -EINVAL;
+ }
+
+ /* Find E1 line */
+ e1_line = e1inp_line_find(ccu_descr->e1_conn_pars->e1_nr);
+ if (!e1_line) {
+ LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "no such E1 line!\n");
+ return -EINVAL;
+ }
+
+ /* Release E1 timeslot */
+ rc = e1inp_ts_config_none(&e1_line->ts[ccu_descr->e1_conn_pars->e1_ts - 1], e1_line);
+ if (rc < 0) {
+ LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "failed to disable E1 timeslot!\n");
+ return -EINVAL;
+ }
+ rc = e1inp_line_update(e1_line);
+ if (rc < 0) {
+ LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "failed to update E1 line!\n");
+ return -EINVAL;
+ }
+
+ ts_descr->usecount--;
+ OSMO_ASSERT(ts_descr->usecount == 0);
+
+ return 0;
+}
+
+int er_ccu_if_open(struct er_ccu_descr *ccu_descr)
+{
+ if (ccu_descr->link.ccu_connected) {
+ LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP",
+ "cannot connect CCU since it is already connected -- ignored!\n");
+ return 0;
+ }
+
+ if (open_e1_timeslot(ccu_descr) < 0)
+ return -EINVAL;
+
+ if (add_i460_subslot(tall_ccu_ctx, ccu_descr) < 0)
+ return -EINVAL;
+
+ ccu_descr->link.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)
+{
+ if (!ccu_descr->link.ccu_connected) {
+ LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP",
+ "cannot disconnect CCU since it is already disconnected -- ignored!\n");
+ return;
+ }
+
+ del_i460_subslot(ccu_descr);
+ close_e1_timeslot(ccu_descr);
+
+ ccu_descr->link.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;
+
+ if (!ccu_descr->link.ccu_connected) {
+ LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "cannot TX block, CCU is disconnected -- ignored!\n");
+ return;
+ }
+
+ msg = msgb_alloc_c(tall_ccu_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->link.schan, msg);
+}
+
+void er_ccu_if_init(void *ctx)
+{
+ libosmo_abis_init(ctx);
+ e1inp_vty_init();
+
+ tall_ccu_ctx = talloc_new(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 00000000..40735301
--- /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 00000000..53ab7bd4
--- /dev/null
+++ b/src/ericsson-rbs/er_ccu_l1_if.c
@@ -0,0 +1,543 @@
+/*
+ * (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 General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <er_ccu_descr.h>
+#include <er_ccu_if.h>
+
+#include <string.h>
+#include <errno.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>
+#include <pcu_l1_if.h>
+#include <pcu_l1_if_phy.h>
+
+extern void *tall_pcu_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 GSM_TDMA_SUPERFRAME * 8
+
+/* 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: PDCH(trx=%u,ts=%u) E1-line(line=%u,ts=%u,ss=%u) " fmt, \
+ tag, ccu_descr->pcu.trx_no, ccu_descr->pcu.ts, \
+ ccu_descr->e1_conn_pars->e1_nr, ccu_descr->e1_conn_pars->e1_ts, \
+ ccu_descr->e1_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL ? 0 : ccu_descr->e1_conn_pars->e1_ts_ss, \
+ ## 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;
+}
+
+static bool mac_block_is_noise(struct er_gprs_trau_frame *trau_frame)
+{
+ switch (trau_frame->u.ccu_data_ind.cs_hdr) {
+ case CS_OR_HDR_CS1:
+ case CS_OR_HDR_CS2:
+ case CS_OR_HDR_CS3:
+ case CS_OR_HDR_CS4:
+ if (!trau_frame->u.ccu_data_ind.u.gprs.parity_ok)
+ return true;
+ break;
+ case CS_OR_HDR_HDR1:
+ case CS_OR_HDR_HDR2:
+ case CS_OR_HDR_HDR3:
+ if (!trau_frame->u.ccu_data_ind.u.egprs.hdr_good)
+ return true;
+ if (!trau_frame->u.ccu_data_ind.u.egprs.data_good[0]
+ && !trau_frame->u.ccu_data_ind.u.egprs.data_good[1])
+ return true;
+ break;
+ case CS_OR_HDR_AB:
+ /* We are not interested in receiving access bursts. */
+ return true;
+ }
+
+ /* No noise, this block is interesting for us. */
+ return false;
+}
+
+static void log_data_ind(struct er_ccu_descr *ccu_descr, struct er_gprs_trau_frame *trau_frame, uint32_t afn_ul_comp,
+ uint32_t afn_dl_comp)
+{
+ switch (trau_frame->u.ccu_data_ind.cs_hdr) {
+ case CS_OR_HDR_CS1:
+ case CS_OR_HDR_CS2:
+ case CS_OR_HDR_CS3:
+ case CS_OR_HDR_CS4:
+ 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);
+ break;
+ case CS_OR_HDR_HDR1:
+ case CS_OR_HDR_HDR2:
+ case CS_OR_HDR_HDR3:
+ case CS_OR_HDR_AB:
+ LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-DATA-IND",
+ "tav=%u, dbe=%u, cs_hdr=%u, rx_lev=%u, est_acc_del_dev=%u,"
+ "mean_bep=%u, cv_bep=%u, hdr_good=%u, data_good[0]=%u, data_good[1]=%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.egprs.mean_bep,
+ trau_frame->u.ccu_data_ind.u.egprs.cv_bep, trau_frame->u.ccu_data_ind.u.egprs.hdr_good,
+ trau_frame->u.ccu_data_ind.u.egprs.data_good[0],
+ trau_frame->u.ccu_data_ind.u.egprs.data_good[1],
+ 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);
+ }
+}
+
+/* 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->sync.last_afn_ul % 13) / 4];
+ inc_dl = fn_inc_table[(ccu_descr->sync.last_afn_dl % 13) / 4];
+ afn_ul = GSM_TDMA_FN_SUM(ccu_descr->sync.last_afn_ul, inc_ul);
+ afn_dl = GSM_TDMA_FN_SUM(ccu_descr->sync.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);
+ afn_dl_comp = afn_dl;
+ afn_dl_comp = fn_dl_advance(afn_dl_comp, GSM_TDMA_FN_DIFF(ccu_descr->sync.pseq_pcu, ccu_descr->sync.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->sync.pseq_pcu, ccu_descr->sync.pseq_ccu, GSM_TDMA_FN_DIFF(ccu_descr->sync.pseq_pcu, ccu_descr->sync.pseq_ccu));
+
+ /* Decode indication from CCU */
+ if (ccu_descr->e1_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL)
+ 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 uplink 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->sync.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->sync.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->sync.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_NOTICE, "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_NOTICE, "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->sync.ccu_synced = false;
+ } else {
+ LOGPL1IF(ccu_descr, LOGL_NOTICE, "CCU-SYNC-IND", "in sync with CCU\n");
+ ccu_descr->sync.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->sync.tav = trau_frame.u.ccu_data_ind.tav;
+
+ /* Ignore all data indications that contain only noise */
+ if (mac_block_is_noise(&trau_frame))
+ break;
+
+ log_data_ind(ccu_descr, &trau_frame, afn_ul_comp, afn_dl_comp);
+
+ /* Hand received MAC block into PCU */
+ bts = gprs_pcu_get_bts_by_nr(the_pcu, ccu_descr->pcu.bts_nr);
+ 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->pcu.trx_no].pdch[ccu_descr->pcu.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->sync.ccu_synced) {
+ bts = gprs_pcu_get_bts_by_nr(the_pcu, ccu_descr->pcu.bts_nr);
+ 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->pcu.trx_no].pdch[ccu_descr->pcu.ts];
+ pcu_rx_block_time(bts, pdch->trx->arfcn, afn_ul_comp, ccu_descr->pcu.ts);
+ rc = pcu_rx_rts_req_pdtch(bts, ccu_descr->pcu.trx_no, ccu_descr->pcu.ts, afn_dl_comp,
+ fn_to_block_nr(afn_dl_comp));
+ }
+ }
+
+ /* We do not receive sync indications in every cycle. When traffic is transferred 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->sync.last_afn_ul = afn_ul;
+ ccu_descr->sync.last_afn_dl = afn_dl;
+ ccu_descr->sync.pseq_pcu++;
+ ccu_descr->sync.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->sync.pseq_pcu;
+ trau_frame.u.pcu_sync_ind.tav = ccu_descr->sync.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_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL)
+ 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->sync.tav = TIME_ADJ_NONE;
+}
+
+/* use the length of the block to determine the coding scheme */
+static int cs_hdr_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;
+ case 27:
+ case 33:
+ case 42:
+ case 49:
+ return CS_OR_HDR_HDR3;
+ case 60:
+ case 78:
+ return CS_OR_HDR_HDR2;
+ case 118:
+ case 142:
+ case 154:
+ return CS_OR_HDR_HDR1;
+ 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_trx_descr *trx_descr = obj;
+ struct er_ccu_descr *ccu_descr;
+ struct er_gprs_trau_frame trau_frame;
+ ubit_t trau_frame_encoded[ER_GPRS_TRAU_FRAME_LEN_64K];
+ struct gprs_rlcmac_bts *bts;
+ int rc;
+
+ /* Make sure that the CCU is synchronized and connected. */
+ if (!trx_descr) {
+ LOGP(DL1IF, LOGL_ERROR, "PCU-DATA-IND: PDCH(ts=%u, arfcn=%u) no TRX context, tossing MAC block...\n",
+ ts, arfcn);
+ return -EINVAL;
+ }
+
+ ccu_descr = &trx_descr->ts_ccu_descr[ts];
+
+ if (!ccu_descr->link.ccu_connected) {
+ LOGPL1IF(ccu_descr, LOGL_NOTICE, "PCU-DATA-IND", "CCU not connected, tossing MAC block...\n");
+ return -EINVAL;
+ }
+ if (!ccu_descr->sync.ccu_synced) {
+ LOGPL1IF(ccu_descr, LOGL_NOTICE, "PCU-DATA-IND", "CCU not synchronized, tossing MAC block...\n");
+ return -EINVAL;
+ }
+
+ /* Hand received MAC block into PCU */
+ bts = gprs_pcu_get_bts_by_nr(the_pcu, ccu_descr->pcu.bts_nr);
+ if (!bts) {
+ LOGPL1IF(ccu_descr, LOGL_NOTICE, "PCU-DATA-IND", "no BTS, tossing MAC block...\n");
+ return -EINVAL;
+ }
+
+ memset(&trau_frame, 0, sizeof(trau_frame));
+ trau_frame.type = ER_GPRS_TRAU_FT_DATA;
+
+ rc = cs_hdr_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->sync.tav;
+ trau_frame.u.pcu_data_ind.ul_frame_err = ccu_descr->sync.ul_frame_err;
+ if (bts->mcs_mask)
+ trau_frame.u.pcu_data_ind.ul_chan_mode = ER_UL_CHMOD_NB_UNKN;
+ else
+ trau_frame.u.pcu_data_ind.ul_chan_mode = ER_UL_CHMOD_NB_GMSK;
+ 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_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL)
+ 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->sync.tav = TIME_ADJ_NONE;
+ ccu_descr->sync.ul_frame_err = false;
+
+ return 0;
+}
+
+void *l1if_open_trx(uint8_t bts_nr, uint8_t trx_no, uint32_t hlayer1, struct gsmtap_inst *gsmtap)
+{
+ struct er_trx_descr *trx_descr;
+ unsigned int i;
+
+ /* Note: We do not have enough information to really open anything at
+ * this point. We will just create the TRX context and fill it wit basic
+ * CCU context (one for each TS) */
+
+ trx_descr = talloc_zero(tall_pcu_ctx, struct er_trx_descr);
+ OSMO_ASSERT(trx_descr);
+
+ for (i = 0; i < ARRAY_SIZE(trx_descr->ts_ccu_descr); i++) {
+ trx_descr->ts_ccu_descr[i].er_ccu_rx_cb = er_ccu_rx_cb;
+ trx_descr->ts_ccu_descr[i].er_ccu_empty_cb = er_ccu_empty_cb;
+ trx_descr->ts_ccu_descr[i].pcu.trx_no = trx_no;
+ trx_descr->ts_ccu_descr[i].pcu.bts_nr = bts_nr;
+ trx_descr->ts_ccu_descr[i].pcu.ts = i;
+ }
+
+ return trx_descr;
+}
+
+int l1if_close_trx(void *obj)
+{
+ struct er_trx_descr *trx_descr = obj;
+ unsigned int i;
+
+ if (!trx_descr) {
+ LOGP(DL1IF, LOGL_ERROR, "PCU-DATA-IND: no TRX context, cannot close unknown TRX...\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(trx_descr->ts_ccu_descr); i++)
+ er_ccu_if_close(&trx_descr->ts_ccu_descr[i]);
+
+ talloc_free(trx_descr);
+ return 0;
+}
+
+int l1if_connect_pdch(void *obj, uint8_t ts)
+{
+ struct er_trx_descr *trx_descr = obj;
+ struct er_ccu_descr *ccu_descr;
+ int rc;
+
+ if (!trx_descr) {
+ LOGP(DL1IF, LOGL_ERROR, "SETUP: PDCH(ts=%u) no CCU context, TRX never opened before?\n", ts);
+ return -EINVAL;
+ }
+
+ ccu_descr = &trx_descr->ts_ccu_descr[ts];
+
+ rc = pcu_l1if_get_e1_ccu_conn_pars(&ccu_descr->e1_conn_pars, ccu_descr->pcu.bts_nr, ccu_descr->pcu.trx_no,
+ ccu_descr->pcu.ts);
+ if (rc < 0) {
+ LOGPL1IF(ccu_descr, LOGL_ERROR, "SETUP", "cannot find E1 connection parameters for CCU\n");
+ return -EINVAL;
+ }
+
+ rc = er_ccu_if_open(ccu_descr);
+ if (rc < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+int l1if_disconnect_pdch(void *obj, uint8_t ts)
+{
+ struct er_trx_descr *trx_descr = obj;
+ struct er_ccu_descr *ccu_descr;
+
+ if (!trx_descr) {
+ LOGP(DL1IF, LOGL_ERROR, "SETUP: PDCH(ts=%u) no TRX context, TRX never opened before?\n", ts);
+ return -EINVAL;
+ }
+
+ ccu_descr = &trx_descr->ts_ccu_descr[ts];
+
+ er_ccu_if_close(ccu_descr);
+
+ return 0;
+}
+
+int l1if_init(void)
+{
+ er_ccu_if_init(tall_pcu_ctx);
+ return 0;
+}