diff options
Diffstat (limited to 'src/ericsson-rbs')
-rw-r--r-- | src/ericsson-rbs/er_ccu_descr.h | 53 | ||||
-rw-r--r-- | src/ericsson-rbs/er_ccu_if.c | 416 | ||||
-rw-r--r-- | src/ericsson-rbs/er_ccu_if.h | 10 | ||||
-rw-r--r-- | src/ericsson-rbs/er_ccu_l1_if.c | 543 |
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..ad7f3530 --- /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 */ + e1inp_ts_send_raw(ts, 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; +} |