osmo-bts-trx: move logical channel handlers to separate files
It's easier to maintain the logical channel handlers in separate files, rather than in a huge one (scheduler_trx.c, ~2k lines). Change-Id: Ie5663fd90596b4800a4546675a323250bbb24c80
This commit is contained in:
parent
0b92bcc37c
commit
36c5ec4881
|
@ -28,6 +28,7 @@ LDADD = \
|
|||
$(NULL)
|
||||
|
||||
noinst_HEADERS = \
|
||||
sched_utils.h \
|
||||
trx_if.h \
|
||||
l1_if.h \
|
||||
loops.h \
|
||||
|
@ -40,6 +41,12 @@ osmo_bts_trx_SOURCES = \
|
|||
trx_if.c \
|
||||
l1_if.c \
|
||||
scheduler_trx.c \
|
||||
sched_lchan_fcch_sch.c \
|
||||
sched_lchan_rach.c \
|
||||
sched_lchan_xcch.c \
|
||||
sched_lchan_pdtch.c \
|
||||
sched_lchan_tchf.c \
|
||||
sched_lchan_tchh.c \
|
||||
trx_vty.c \
|
||||
loops.c \
|
||||
$(NULL)
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* 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 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 <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
|
||||
#include <osmo-bts/bts.h>
|
||||
#include <osmo-bts/l1sap.h>
|
||||
#include <osmo-bts/logging.h>
|
||||
#include <osmo-bts/scheduler.h>
|
||||
#include <osmo-bts/scheduler_backend.h>
|
||||
|
||||
#include <sched_utils.h>
|
||||
|
||||
/* obtain a to-be-transmitted FCCH (frequency correction channel) burst */
|
||||
ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
|
||||
enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
|
||||
{
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting FCCH\n");
|
||||
|
||||
if (nbits)
|
||||
*nbits = GSM_BURST_LEN;
|
||||
|
||||
/* BURST BYPASS */
|
||||
|
||||
return (ubit_t *) _sched_fcch_burst;
|
||||
}
|
||||
|
||||
/* obtain a to-be-transmitted SCH (synchronization channel) burst */
|
||||
ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
|
||||
enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
|
||||
{
|
||||
static ubit_t bits[GSM_BURST_LEN], burst[78];
|
||||
uint8_t sb_info[4];
|
||||
struct gsm_time t;
|
||||
uint8_t t3p, bsic;
|
||||
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting SCH\n");
|
||||
|
||||
/* BURST BYPASS */
|
||||
|
||||
/* create SB info from GSM time and BSIC */
|
||||
gsm_fn2gsmtime(&t, fn);
|
||||
t3p = t.t3 / 10;
|
||||
bsic = l1t->trx->bts->bsic;
|
||||
sb_info[0] =
|
||||
((bsic & 0x3f) << 2) |
|
||||
((t.t1 & 0x600) >> 9);
|
||||
sb_info[1] =
|
||||
((t.t1 & 0x1fe) >> 1);
|
||||
sb_info[2] =
|
||||
((t.t1 & 0x001) << 7) |
|
||||
((t.t2 & 0x1f) << 2) |
|
||||
((t3p & 0x6) >> 1);
|
||||
sb_info[3] =
|
||||
(t3p & 0x1);
|
||||
|
||||
/* encode bursts */
|
||||
gsm0503_sch_encode(burst, sb_info);
|
||||
|
||||
/* compose burst */
|
||||
memset(bits, 0, 3);
|
||||
memcpy(bits + 3, burst, 39);
|
||||
memcpy(bits + 42, _sched_sch_train, 64);
|
||||
memcpy(bits + 106, burst + 39, 39);
|
||||
memset(bits + 145, 0, 3);
|
||||
|
||||
if (nbits)
|
||||
*nbits = GSM_BURST_LEN;
|
||||
|
||||
return bits;
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* 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 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 <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
|
||||
#include <osmo-bts/bts.h>
|
||||
#include <osmo-bts/l1sap.h>
|
||||
#include <osmo-bts/logging.h>
|
||||
#include <osmo-bts/scheduler.h>
|
||||
#include <osmo-bts/scheduler_backend.h>
|
||||
|
||||
#include <sched_utils.h>
|
||||
|
||||
/* Maximum size of a EGPRS message in bytes */
|
||||
#define EGPRS_0503_MAX_BYTES 155
|
||||
|
||||
/*! \brief a single PDTCH burst was received by the PHY, process it */
|
||||
int rx_pdtch_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
|
||||
uint8_t bid, const struct trx_ul_burst_ind *bi)
|
||||
{
|
||||
struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
|
||||
struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
|
||||
sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
|
||||
uint32_t *first_fn = &chan_state->ul_first_fn;
|
||||
uint8_t *mask = &chan_state->ul_mask;
|
||||
float *rssi_sum = &chan_state->rssi_sum;
|
||||
uint8_t *rssi_num = &chan_state->rssi_num;
|
||||
int32_t *toa256_sum = &chan_state->toa256_sum;
|
||||
uint8_t *toa_num = &chan_state->toa_num;
|
||||
int32_t *ci_cb_sum = &chan_state->ci_cb_sum;
|
||||
uint8_t *ci_cb_num = &chan_state->ci_cb_num;
|
||||
uint8_t l2[EGPRS_0503_MAX_BYTES];
|
||||
int n_errors = 0;
|
||||
int n_bursts_bits = 0;
|
||||
int n_bits_total = 0;
|
||||
int16_t lqual_cb;
|
||||
uint16_t ber10k;
|
||||
int rc;
|
||||
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Received PDTCH bid=%u\n", bid);
|
||||
|
||||
/* allocate burst memory, if not already */
|
||||
if (!*bursts_p) {
|
||||
*bursts_p = talloc_zero_size(tall_bts_ctx,
|
||||
GSM0503_EGPRS_BURSTS_NBITS);
|
||||
if (!*bursts_p)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* clear burst */
|
||||
if (bid == 0) {
|
||||
memset(*bursts_p, 0, GSM0503_EGPRS_BURSTS_NBITS);
|
||||
*mask = 0x0;
|
||||
*first_fn = bi->fn;
|
||||
*rssi_sum = 0;
|
||||
*rssi_num = 0;
|
||||
*toa256_sum = 0;
|
||||
*toa_num = 0;
|
||||
*ci_cb_sum = 0;
|
||||
*ci_cb_num = 0;
|
||||
}
|
||||
|
||||
/* update mask + rssi */
|
||||
*mask |= (1 << bid);
|
||||
*rssi_sum += bi->rssi;
|
||||
(*rssi_num)++;
|
||||
*toa256_sum += bi->toa256;
|
||||
(*toa_num)++;
|
||||
|
||||
/* C/I: Carrier-to-Interference ratio (in centiBels) */
|
||||
if (bi->flags & TRX_BI_F_CI_CB) {
|
||||
*ci_cb_sum += bi->ci_cb;
|
||||
(*ci_cb_num)++;
|
||||
}
|
||||
|
||||
/* copy burst to buffer of 4 bursts */
|
||||
if (bi->burst_len == EGPRS_BURST_LEN) {
|
||||
burst = *bursts_p + bid * 348;
|
||||
memcpy(burst, bi->burst + 9, 174);
|
||||
memcpy(burst + 174, bi->burst + 261, 174);
|
||||
n_bursts_bits = GSM0503_EGPRS_BURSTS_NBITS;
|
||||
} else {
|
||||
burst = *bursts_p + bid * 116;
|
||||
memcpy(burst, bi->burst + 3, 58);
|
||||
memcpy(burst + 58, bi->burst + 87, 58);
|
||||
n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS;
|
||||
}
|
||||
|
||||
/* wait until complete set of bursts */
|
||||
if (bid != 3)
|
||||
return 0;
|
||||
|
||||
/* check for complete set of bursts */
|
||||
if ((*mask & 0xf) != 0xf) {
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Received incomplete frame (%u/%u)\n",
|
||||
bi->fn % l1ts->mf_period, l1ts->mf_period);
|
||||
}
|
||||
*mask = 0x0;
|
||||
|
||||
/*
|
||||
* Attempt to decode EGPRS bursts first. For 8-PSK EGPRS this is all we
|
||||
* do. Attempt GPRS decoding on EGPRS failure. If the burst is GPRS,
|
||||
* then we incur decoding overhead of 31 bits on the Type 3 EGPRS
|
||||
* header, which is tolerable.
|
||||
*/
|
||||
rc = gsm0503_pdtch_egprs_decode(l2, *bursts_p, n_bursts_bits,
|
||||
NULL, &n_errors, &n_bits_total);
|
||||
|
||||
if ((bi->burst_len == GSM_BURST_LEN) && (rc < 0)) {
|
||||
rc = gsm0503_pdtch_decode(l2, *bursts_p, NULL,
|
||||
&n_errors, &n_bits_total);
|
||||
}
|
||||
|
||||
if (rc <= 0) {
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Received bad PDTCH (%u/%u)\n",
|
||||
bi->fn % l1ts->mf_period, l1ts->mf_period);
|
||||
return 0;
|
||||
}
|
||||
|
||||
lqual_cb = *ci_cb_num ? (*ci_cb_sum / *ci_cb_num) : 0;
|
||||
ber10k = compute_ber10k(n_bits_total, n_errors);
|
||||
return _sched_compose_ph_data_ind(l1t, bi->tn,
|
||||
*first_fn, chan, l2, rc,
|
||||
*rssi_sum / *rssi_num,
|
||||
*toa256_sum / *toa_num,
|
||||
lqual_cb, ber10k,
|
||||
PRES_INFO_BOTH);
|
||||
}
|
||||
|
||||
/* obtain a to-be-transmitted PDTCH (packet data) burst */
|
||||
ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
|
||||
enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
|
||||
{
|
||||
struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
|
||||
struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
|
||||
struct msgb *msg = NULL; /* make GCC happy */
|
||||
ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts;
|
||||
enum trx_burst_type *burst_type = &l1ts->chan_state[chan].dl_burst_type;
|
||||
static ubit_t bits[EGPRS_BURST_LEN];
|
||||
int rc = 0;
|
||||
|
||||
/* send burst, if we already got a frame */
|
||||
if (bid > 0) {
|
||||
if (!*bursts_p)
|
||||
return NULL;
|
||||
goto send_burst;
|
||||
}
|
||||
|
||||
/* get mac block from queue */
|
||||
msg = _sched_dequeue_prim(l1t, tn, fn, chan);
|
||||
if (msg)
|
||||
goto got_msg;
|
||||
|
||||
LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n");
|
||||
|
||||
no_msg:
|
||||
/* free burst memory */
|
||||
if (*bursts_p) {
|
||||
talloc_free(*bursts_p);
|
||||
*bursts_p = NULL;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
got_msg:
|
||||
/* BURST BYPASS */
|
||||
|
||||
/* allocate burst memory, if not already */
|
||||
if (!*bursts_p) {
|
||||
*bursts_p = talloc_zero_size(tall_bts_ctx,
|
||||
GSM0503_EGPRS_BURSTS_NBITS);
|
||||
if (!*bursts_p)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* encode bursts */
|
||||
rc = gsm0503_pdtch_egprs_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h);
|
||||
if (rc < 0)
|
||||
rc = gsm0503_pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h);
|
||||
|
||||
/* check validity of message */
|
||||
if (rc < 0) {
|
||||
LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim invalid length, please FIX! "
|
||||
"(len=%ld)\n", (long)(msg->tail - msg->l2h));
|
||||
/* free message */
|
||||
msgb_free(msg);
|
||||
goto no_msg;
|
||||
} else if (rc == GSM0503_EGPRS_BURSTS_NBITS) {
|
||||
*burst_type = TRX_BURST_8PSK;
|
||||
} else {
|
||||
*burst_type = TRX_BURST_GMSK;
|
||||
}
|
||||
|
||||
/* free message */
|
||||
msgb_free(msg);
|
||||
|
||||
send_burst:
|
||||
/* compose burst */
|
||||
if (*burst_type == TRX_BURST_8PSK) {
|
||||
burst = *bursts_p + bid * 348;
|
||||
memset(bits, 1, 9);
|
||||
memcpy(bits + 9, burst, 174);
|
||||
memcpy(bits + 183, _sched_egprs_tsc[gsm_ts_tsc(ts)], 78);
|
||||
memcpy(bits + 261, burst + 174, 174);
|
||||
memset(bits + 435, 1, 9);
|
||||
|
||||
if (nbits)
|
||||
*nbits = EGPRS_BURST_LEN;
|
||||
} else {
|
||||
burst = *bursts_p + bid * 116;
|
||||
memset(bits, 0, 3);
|
||||
memcpy(bits + 3, burst, 58);
|
||||
memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
|
||||
memcpy(bits + 87, burst + 58, 58);
|
||||
memset(bits + 145, 0, 3);
|
||||
|
||||
if (nbits)
|
||||
*nbits = GSM_BURST_LEN;
|
||||
}
|
||||
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
|
||||
|
||||
return bits;
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2019 by Vadim Yanitskiy <axilirator@gmail.com>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* 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 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 <stdint.h>
|
||||
#include <limits.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
|
||||
#include <osmo-bts/bts.h>
|
||||
#include <osmo-bts/l1sap.h>
|
||||
#include <osmo-bts/logging.h>
|
||||
#include <osmo-bts/scheduler.h>
|
||||
#include <osmo-bts/scheduler_backend.h>
|
||||
|
||||
#include <sched_utils.h>
|
||||
|
||||
/* 3GPP TS 05.02, section 5.2.7 */
|
||||
#define RACH_EXT_TAIL_LEN 8
|
||||
#define RACH_SYNCH_SEQ_LEN 41
|
||||
|
||||
enum rach_synch_seq_t {
|
||||
RACH_SYNCH_SEQ_UNKNOWN = -1,
|
||||
RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */
|
||||
RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */
|
||||
RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */
|
||||
RACH_SYNCH_SEQ_NUM
|
||||
};
|
||||
|
||||
static struct value_string rach_synch_seq_names[] = {
|
||||
{ RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" },
|
||||
{ RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" },
|
||||
{ RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" },
|
||||
{ RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" },
|
||||
{ 0, NULL },
|
||||
};
|
||||
|
||||
static enum rach_synch_seq_t rach_get_synch_seq(sbit_t *bits, int *best_score)
|
||||
{
|
||||
sbit_t *synch_seq_burst = bits + RACH_EXT_TAIL_LEN;
|
||||
enum rach_synch_seq_t seq = RACH_SYNCH_SEQ_TS0;
|
||||
int score[RACH_SYNCH_SEQ_NUM] = { 0 };
|
||||
int max_score = INT_MIN;
|
||||
int i, j;
|
||||
|
||||
/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
|
||||
static const char synch_seq_ref[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = {
|
||||
[RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000",
|
||||
[RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101",
|
||||
[RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111",
|
||||
};
|
||||
|
||||
/* Get a multiplier for j-th bit of i-th synch. sequence */
|
||||
#define RACH_SYNCH_SEQ_MULT \
|
||||
(synch_seq_ref[i][j] == '1' ? -1 : 1)
|
||||
|
||||
/* For each synch. sequence, count the bit match score. Since we deal with
|
||||
* soft-bits (-127...127), we sum the absolute values of matching ones,
|
||||
* and subtract the absolute values of different ones, so the resulting
|
||||
* score is more accurate than it could be with hard-bits. */
|
||||
for (i = 0; i < RACH_SYNCH_SEQ_NUM; i++) {
|
||||
for (j = 0; j < RACH_SYNCH_SEQ_LEN; j++)
|
||||
score[i] += RACH_SYNCH_SEQ_MULT * synch_seq_burst[j];
|
||||
|
||||
/* Keep the maximum value updated */
|
||||
if (score[i] > max_score) {
|
||||
max_score = score[i];
|
||||
seq = i;
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate an approximate level of our confidence */
|
||||
if (best_score != NULL)
|
||||
*best_score = max_score;
|
||||
|
||||
/* At least 1/3 of a synch. sequence shall match */
|
||||
if (max_score < (127 * RACH_SYNCH_SEQ_LEN / 3))
|
||||
return RACH_SYNCH_SEQ_UNKNOWN;
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
int rx_rach_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
|
||||
uint8_t bid, const struct trx_ul_burst_ind *bi)
|
||||
{
|
||||
struct osmo_phsap_prim l1sap;
|
||||
int n_errors = 0;
|
||||
int n_bits_total = 0;
|
||||
uint16_t ra11;
|
||||
uint8_t ra;
|
||||
int rc;
|
||||
|
||||
/* TSC (Training Sequence Code) is an optional parameter of the UL burst
|
||||
* indication. We need this information in order to decide whether an
|
||||
* Access Burst is 11-bit encoded or not (see OS#1854). If this information
|
||||
* is absent, we try to correlate the received synch. sequence with the
|
||||
* known ones (3GPP TS 05.02, section 5.2.7), and fall-back to the default
|
||||
* TS0 if it fails. */
|
||||
enum rach_synch_seq_t synch_seq = RACH_SYNCH_SEQ_TS0;
|
||||
int best_score = 127 * RACH_SYNCH_SEQ_LEN;
|
||||
|
||||
/* If logical channel is not either of RACH, PDTCH or PTCCH, this is a
|
||||
* handover Access Burst, which is always encoded as 8-bit and shall
|
||||
* contain the generic training sequence (TS0). */
|
||||
if (chan == TRXC_RACH || chan == TRXC_PDTCH || chan == TRXC_PTCCH) {
|
||||
if (bi->flags & TRX_BI_F_TS_INFO)
|
||||
synch_seq = (enum rach_synch_seq_t) bi->tsc;
|
||||
else
|
||||
synch_seq = rach_get_synch_seq((sbit_t *) bi->burst, &best_score);
|
||||
}
|
||||
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Received%s RACH (%s): rssi=%d toa256=%d",
|
||||
(chan != TRXC_RACH) ? " handover" : "",
|
||||
get_value_string(rach_synch_seq_names, synch_seq),
|
||||
bi->rssi, bi->toa256);
|
||||
if (bi->flags & TRX_BI_F_CI_CB)
|
||||
LOGPC(DL1P, LOGL_DEBUG, " C/I=%d cB", bi->ci_cb);
|
||||
else
|
||||
LOGPC(DL1P, LOGL_DEBUG, " match=%.1f%%",
|
||||
best_score * 100.0 / (127 * RACH_SYNCH_SEQ_LEN));
|
||||
LOGPC(DL1P, LOGL_DEBUG, "\n");
|
||||
|
||||
/* Compose a new L1SAP primitive */
|
||||
memset(&l1sap, 0x00, sizeof(l1sap));
|
||||
osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL);
|
||||
l1sap.u.rach_ind.chan_nr = trx_chan_desc[chan].chan_nr | bi->tn;
|
||||
l1sap.u.rach_ind.acc_delay = (bi->toa256 >= 0) ? bi->toa256 / 256 : 0;
|
||||
l1sap.u.rach_ind.acc_delay_256bits = bi->toa256;
|
||||
l1sap.u.rach_ind.rssi = bi->rssi;
|
||||
l1sap.u.rach_ind.fn = bi->fn;
|
||||
|
||||
/* Link quality is defined by C/I (Carrier-to-Interference ratio),
|
||||
* which has optional presence. If it's absent, report the
|
||||
* minimum acceptable value to pass L1SAP checks. */
|
||||
if (bi->flags & TRX_BI_F_CI_CB)
|
||||
l1sap.u.rach_ind.lqual_cb = bi->ci_cb;
|
||||
else
|
||||
l1sap.u.rach_ind.lqual_cb = l1t->trx->bts->min_qual_rach;
|
||||
|
||||
/* Decode RACH depending on its synch. sequence */
|
||||
switch (synch_seq) {
|
||||
case RACH_SYNCH_SEQ_TS1:
|
||||
case RACH_SYNCH_SEQ_TS2:
|
||||
rc = gsm0503_rach_ext_decode_ber(&ra11, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN,
|
||||
l1t->trx->bts->bsic, &n_errors, &n_bits_total);
|
||||
if (rc) {
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Received bad Access Burst\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (synch_seq == RACH_SYNCH_SEQ_TS1)
|
||||
l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_1;
|
||||
else
|
||||
l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_2;
|
||||
|
||||
l1sap.u.rach_ind.is_11bit = 1;
|
||||
l1sap.u.rach_ind.ra = ra11;
|
||||
break;
|
||||
|
||||
case RACH_SYNCH_SEQ_TS0:
|
||||
default:
|
||||
/* Fall-back to the default TS0 if needed */
|
||||
if (synch_seq != RACH_SYNCH_SEQ_TS0) {
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Falling-back to the default TS0\n");
|
||||
synch_seq = RACH_SYNCH_SEQ_TS0;
|
||||
}
|
||||
|
||||
rc = gsm0503_rach_decode_ber(&ra, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN,
|
||||
l1t->trx->bts->bsic, &n_errors, &n_bits_total);
|
||||
if (rc) {
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Received bad Access Burst\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0;
|
||||
l1sap.u.rach_ind.is_11bit = 0;
|
||||
l1sap.u.rach_ind.ra = ra;
|
||||
break;
|
||||
}
|
||||
|
||||
l1sap.u.rach_ind.ber10k = compute_ber10k(n_bits_total, n_errors);
|
||||
|
||||
/* forward primitive */
|
||||
l1sap_up(l1t->trx, &l1sap);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,570 @@
|
|||
/*
|
||||
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* 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 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 <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <osmocom/gsm/gsm0502.h>
|
||||
|
||||
#include <osmocom/codec/codec.h>
|
||||
#include <osmocom/codec/ecu.h>
|
||||
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
#include <osmocom/coding/gsm0503_amr_dtx.h>
|
||||
|
||||
#include <osmo-bts/bts.h>
|
||||
#include <osmo-bts/l1sap.h>
|
||||
#include <osmo-bts/logging.h>
|
||||
#include <osmo-bts/scheduler.h>
|
||||
#include <osmo-bts/scheduler_backend.h>
|
||||
#include <osmo-bts/msg_utils.h>
|
||||
|
||||
#include <sched_utils.h>
|
||||
#include <loops.h>
|
||||
|
||||
/*! \brief a single TCH/F burst was received by the PHY, process it */
|
||||
int rx_tchf_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
|
||||
uint8_t bid, const struct trx_ul_burst_ind *bi)
|
||||
{
|
||||
struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
|
||||
struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
|
||||
sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
|
||||
uint8_t *mask = &chan_state->ul_mask;
|
||||
uint8_t rsl_cmode = chan_state->rsl_cmode;
|
||||
uint8_t tch_mode = chan_state->tch_mode;
|
||||
uint8_t tch_data[128]; /* just to be safe */
|
||||
int rc, amr = 0;
|
||||
int n_errors = 0;
|
||||
int n_bits_total = 0;
|
||||
bool bfi_flag = false;
|
||||
struct gsm_lchan *lchan =
|
||||
get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | bi->tn);
|
||||
unsigned int fn_begin;
|
||||
uint16_t ber10k;
|
||||
uint8_t is_sub = 0;
|
||||
uint8_t ft;
|
||||
|
||||
/* If handover RACH detection is turned on, treat this burst as an Access Burst.
|
||||
* Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
|
||||
if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
|
||||
return rx_rach_fn(l1t, chan, bid, bi);
|
||||
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Received TCH/F, bid=%u\n", bid);
|
||||
|
||||
/* allocate burst memory, if not already */
|
||||
if (!*bursts_p) {
|
||||
*bursts_p = talloc_zero_size(tall_bts_ctx, 928);
|
||||
if (!*bursts_p)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* clear burst */
|
||||
if (bid == 0) {
|
||||
memset(*bursts_p + 464, 0, 464);
|
||||
*mask = 0x0;
|
||||
}
|
||||
|
||||
/* update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* copy burst to end of buffer of 8 bursts */
|
||||
burst = *bursts_p + bid * 116 + 464;
|
||||
if (bi->burst_len > 0) {
|
||||
memcpy(burst, bi->burst + 3, 58);
|
||||
memcpy(burst + 58, bi->burst + 87, 58);
|
||||
} else
|
||||
memset(burst, 0, 116);
|
||||
|
||||
/* wait until complete set of bursts */
|
||||
if (bid != 3)
|
||||
return 0;
|
||||
|
||||
/* check for complete set of bursts */
|
||||
if ((*mask & 0xf) != 0xf) {
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
|
||||
"Received incomplete frame (%u/%u)\n",
|
||||
bi->fn % l1ts->mf_period, l1ts->mf_period);
|
||||
}
|
||||
*mask = 0x0;
|
||||
|
||||
/* decode
|
||||
* also shift buffer by 4 bursts for interleaving */
|
||||
switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1
|
||||
: tch_mode) {
|
||||
case GSM48_CMODE_SPEECH_V1: /* FR */
|
||||
rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 0, &n_errors, &n_bits_total);
|
||||
if (rc >= 0)
|
||||
lchan_set_marker(osmo_fr_check_sid(tch_data, rc), lchan); /* DTXu */
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_EFR: /* EFR */
|
||||
rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 1, &n_errors, &n_bits_total);
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
/* the first FN 0,8,17 defines that CMI is included in frame,
|
||||
* the first FN 4,13,21 defines that CMR is included in frame.
|
||||
* NOTE: A frame ends 7 FN after start.
|
||||
*/
|
||||
|
||||
/* The AFS_ONSET frame itself does not result into an RTP frame
|
||||
* since it only contains a recognition pattern that marks the
|
||||
* end of the DTX interval. To mark the end of the DTX interval
|
||||
* in the RTP stream as well, the voice frame after the
|
||||
* AFS_ONSET frame is used. */
|
||||
if (chan_state->amr_last_dtx == AFS_ONSET)
|
||||
lchan_set_marker(false, lchan);
|
||||
|
||||
/* we store tch_data + 2 header bytes, the amr variable set to
|
||||
* 2 will allow us to skip the first 2 bytes in case we did
|
||||
* receive an FACCH frame instead of a voice frame (we do not
|
||||
* know this before we actually decode the frame) */
|
||||
amr = 2;
|
||||
rc = gsm0503_tch_afs_decode_dtx(tch_data + amr, *bursts_p,
|
||||
(((bi->fn + 26 - 7) % 26) >> 2) & 1, chan_state->codec,
|
||||
chan_state->codecs, &chan_state->ul_ft,
|
||||
&chan_state->ul_cmr, &n_errors, &n_bits_total, &chan_state->amr_last_dtx);
|
||||
|
||||
/* Tag all frames that are not regular AMR voice frames as
|
||||
* SUB-Frames */
|
||||
if (chan_state->amr_last_dtx != AMR_OTHER) {
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Received AMR SID frame: %s\n",
|
||||
gsm0503_amr_dtx_frame_name(chan_state->amr_last_dtx));
|
||||
is_sub = 1;
|
||||
}
|
||||
|
||||
/* The occurrence of the following frames indicates that we
|
||||
* are either at the beginning or in the middle of a talk
|
||||
* spurt. We update the SID status accordingly, but we do
|
||||
* not want the marker to be set, since this must only
|
||||
* happen when the talk spurt is over (see above) */
|
||||
switch (chan_state->amr_last_dtx) {
|
||||
case AFS_SID_FIRST:
|
||||
case AFS_SID_UPDATE:
|
||||
case AFS_SID_UPDATE_CN:
|
||||
lchan_set_marker(true, lchan);
|
||||
lchan->rtp_tx_marker = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc)
|
||||
trx_loop_amr_input(l1t,
|
||||
trx_chan_desc[chan].chan_nr | bi->tn, chan_state,
|
||||
n_errors, n_bits_total);
|
||||
/* only good speech frames get rtp header */
|
||||
if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
|
||||
if (chan_state->amr_last_dtx == AMR_OTHER) {
|
||||
ft = chan_state->codec[chan_state->ul_cmr];
|
||||
} else {
|
||||
/* SID frames will always get Frame Type Index 8 (AMR_SID) */
|
||||
ft = AMR_SID;
|
||||
}
|
||||
rc = osmo_amr_rtp_enc(tch_data,
|
||||
chan_state->codec[chan_state->ul_cmr],
|
||||
ft, AMR_GOOD);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
|
||||
"TCH mode %u invalid, please fix!\n",
|
||||
tch_mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
memcpy(*bursts_p, *bursts_p + 464, 464);
|
||||
|
||||
/* Check if the frame is bad */
|
||||
if (rc < 0) {
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
|
||||
"Received bad data (%u/%u)\n",
|
||||
bi->fn % l1ts->mf_period, l1ts->mf_period);
|
||||
bfi_flag = true;
|
||||
} else if (rc < 4) {
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
|
||||
"Received bad data (%u/%u) with invalid codec mode %d\n",
|
||||
bi->fn % l1ts->mf_period, l1ts->mf_period, rc);
|
||||
bfi_flag = true;
|
||||
}
|
||||
|
||||
if (rc != GSM_MACBLOCK_LEN && lchan->ecu_state)
|
||||
osmo_ecu_frame_in(lchan->ecu_state, bfi_flag, tch_data, rc);
|
||||
|
||||
ber10k = compute_ber10k(n_bits_total, n_errors);
|
||||
if (bfi_flag)
|
||||
goto bfi;
|
||||
|
||||
/* FACCH */
|
||||
if (rc == GSM_MACBLOCK_LEN) {
|
||||
fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_FACCH_F);
|
||||
_sched_compose_ph_data_ind(l1t, bi->tn, fn_begin, chan,
|
||||
tch_data + amr, GSM_MACBLOCK_LEN,
|
||||
/* FIXME: AVG RSSI and ToA256 */
|
||||
bi->rssi, bi->toa256,
|
||||
0 /* FIXME: AVG C/I */,
|
||||
ber10k, PRES_INFO_UNKNOWN);
|
||||
bfi:
|
||||
if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
|
||||
/* indicate bad frame */
|
||||
if (lchan->tch.dtx.ul_sid) {
|
||||
/* DTXu: pause in progress. Push empty payload to upper layers */
|
||||
rc = 0;
|
||||
goto compose_l1sap;
|
||||
}
|
||||
|
||||
/* If there is an ECU active on this channel, use its output */
|
||||
if (lchan->ecu_state) {
|
||||
rc = osmo_ecu_frame_out(lchan->ecu_state, tch_data);
|
||||
if (rc >= 0) /* Otherwise we send a BFI */
|
||||
goto compose_l1sap;
|
||||
}
|
||||
|
||||
switch (tch_mode) {
|
||||
case GSM48_CMODE_SPEECH_V1: /* FR */
|
||||
memset(tch_data, 0, GSM_FR_BYTES);
|
||||
tch_data[0] = 0xd0;
|
||||
rc = GSM_FR_BYTES;
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_EFR: /* EFR */
|
||||
memset(tch_data, 0, GSM_EFR_BYTES);
|
||||
tch_data[0] = 0xc0;
|
||||
rc = GSM_EFR_BYTES;
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
rc = osmo_amr_rtp_enc(tch_data,
|
||||
chan_state->codec[chan_state->dl_cmr],
|
||||
chan_state->codec[chan_state->dl_ft],
|
||||
AMR_BAD);
|
||||
if (rc < 2) {
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
|
||||
"Failed to encode AMR_BAD frame (rc=%d), "
|
||||
"not sending BFI\n", rc);
|
||||
return -EINVAL;
|
||||
}
|
||||
memset(tch_data + 2, 0, rc - 2);
|
||||
break;
|
||||
default:
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
|
||||
"TCH mode %u invalid, please fix!\n", tch_mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
|
||||
return 0;
|
||||
|
||||
/* TCH or BFI */
|
||||
compose_l1sap:
|
||||
fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_TCH_F);
|
||||
return _sched_compose_tch_ind(l1t, bi->tn, fn_begin, chan,
|
||||
tch_data, rc, bi->toa256, ber10k, bi->rssi, is_sub);
|
||||
}
|
||||
|
||||
/* common section for generation of TCH bursts (TCH/H and TCH/F).
|
||||
* FIXME: this function is over-complicated, refactor / get rid of it. */
|
||||
void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
|
||||
enum trx_chan_type chan, uint8_t bid,
|
||||
struct msgb **_msg_tch, struct msgb **_msg_facch)
|
||||
{
|
||||
struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
|
||||
struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL;
|
||||
struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
|
||||
uint8_t rsl_cmode = chan_state->rsl_cmode;
|
||||
uint8_t tch_mode = chan_state->tch_mode;
|
||||
struct osmo_phsap_prim *l1sap;
|
||||
int32_t *toa256_sum = &chan_state->toa256_sum;
|
||||
uint8_t *toa_num = &chan_state->toa_num;
|
||||
int16_t toa256;
|
||||
|
||||
/* handle loss detection of received TCH frames */
|
||||
if (rsl_cmode == RSL_CMOD_SPD_SPEECH
|
||||
&& ++(chan_state->lost_frames) > 5) {
|
||||
uint8_t tch_data[GSM_FR_BYTES];
|
||||
int len;
|
||||
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
|
||||
"Missing TCH bursts detected, sending BFI\n");
|
||||
|
||||
/* indicate bad frame */
|
||||
switch (tch_mode) {
|
||||
case GSM48_CMODE_SPEECH_V1: /* FR / HR */
|
||||
if (chan != TRXC_TCHF) { /* HR */
|
||||
tch_data[0] = 0x70; /* F = 0, FT = 111 */
|
||||
memset(tch_data + 1, 0, 14);
|
||||
len = 15;
|
||||
break;
|
||||
}
|
||||
memset(tch_data, 0, GSM_FR_BYTES);
|
||||
len = GSM_FR_BYTES;
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_EFR: /* EFR */
|
||||
if (chan != TRXC_TCHF)
|
||||
goto inval_mode1;
|
||||
memset(tch_data, 0, GSM_EFR_BYTES);
|
||||
len = GSM_EFR_BYTES;
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
len = osmo_amr_rtp_enc(tch_data,
|
||||
chan_state->codec[chan_state->dl_cmr],
|
||||
chan_state->codec[chan_state->dl_ft], AMR_BAD);
|
||||
if (len < 2) {
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
|
||||
"Failed to encode AMR_BAD frame (rc=%d), "
|
||||
"not sending BFI\n", len);
|
||||
return;
|
||||
}
|
||||
memset(tch_data + 2, 0, len - 2);
|
||||
break;
|
||||
default:
|
||||
inval_mode1:
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
|
||||
len = 0;
|
||||
}
|
||||
|
||||
if (len) {
|
||||
if (*toa_num == 0)
|
||||
toa256 = 0;
|
||||
else
|
||||
toa256 = *toa256_sum / *toa_num;
|
||||
|
||||
/* Note: RSSI is set to 0 to indicate to the higher
|
||||
* layers that this is a faked tch_ind */
|
||||
_sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len, toa256, 10000, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* get frame and unlink from queue */
|
||||
msg1 = _sched_dequeue_prim(l1t, tn, fn, chan);
|
||||
msg2 = _sched_dequeue_prim(l1t, tn, fn, chan);
|
||||
if (msg1) {
|
||||
l1sap = msgb_l1sap_prim(msg1);
|
||||
if (l1sap->oph.primitive == PRIM_TCH) {
|
||||
msg_tch = msg1;
|
||||
if (msg2) {
|
||||
l1sap = msgb_l1sap_prim(msg2);
|
||||
if (l1sap->oph.primitive == PRIM_TCH) {
|
||||
LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
|
||||
"TCH twice, please FIX!\n");
|
||||
msgb_free(msg2);
|
||||
} else
|
||||
msg_facch = msg2;
|
||||
}
|
||||
} else {
|
||||
msg_facch = msg1;
|
||||
if (msg2) {
|
||||
l1sap = msgb_l1sap_prim(msg2);
|
||||
if (l1sap->oph.primitive != PRIM_TCH) {
|
||||
LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
|
||||
"FACCH twice, please FIX!\n");
|
||||
msgb_free(msg2);
|
||||
} else
|
||||
msg_tch = msg2;
|
||||
}
|
||||
}
|
||||
} else if (msg2) {
|
||||
l1sap = msgb_l1sap_prim(msg2);
|
||||
if (l1sap->oph.primitive == PRIM_TCH)
|
||||
msg_tch = msg2;
|
||||
else
|
||||
msg_facch = msg2;
|
||||
}
|
||||
|
||||
/* check validity of message */
|
||||
if (msg_facch && msgb_l2len(msg_facch) != GSM_MACBLOCK_LEN) {
|
||||
LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! "
|
||||
"(len=%d)\n", msgb_l2len(msg_facch));
|
||||
/* free message */
|
||||
msgb_free(msg_facch);
|
||||
msg_facch = NULL;
|
||||
}
|
||||
|
||||
/* check validity of message, get AMR ft and cmr */
|
||||
if (!msg_facch && msg_tch) {
|
||||
int len;
|
||||
uint8_t cmr_codec;
|
||||
int cmr, ft, i;
|
||||
enum osmo_amr_type ft_codec;
|
||||
enum osmo_amr_quality bfi;
|
||||
int8_t sti, cmi;
|
||||
|
||||
if (rsl_cmode != RSL_CMOD_SPD_SPEECH) {
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, "
|
||||
"because we are not in speech mode\n");
|
||||
goto free_bad_msg;
|
||||
}
|
||||
|
||||
switch (tch_mode) {
|
||||
case GSM48_CMODE_SPEECH_V1: /* FR / HR */
|
||||
if (chan != TRXC_TCHF) /* HR */
|
||||
len = 15;
|
||||
else
|
||||
len = GSM_FR_BYTES;
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_EFR: /* EFR */
|
||||
if (chan != TRXC_TCHF)
|
||||
goto inval_mode2;
|
||||
len = GSM_EFR_BYTES;
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
len = osmo_amr_rtp_dec(msg_tch->l2h, msgb_l2len(msg_tch),
|
||||
&cmr_codec, &cmi, &ft_codec,
|
||||
&bfi, &sti);
|
||||
cmr = -1;
|
||||
ft = -1;
|
||||
for (i = 0; i < chan_state->codecs; i++) {
|
||||
if (chan_state->codec[i] == cmr_codec)
|
||||
cmr = i;
|
||||
if (chan_state->codec[i] == ft_codec)
|
||||
ft = i;
|
||||
}
|
||||
if (cmr >= 0) { /* new request */
|
||||
chan_state->dl_cmr = cmr;
|
||||
/* disable AMR loop */
|
||||
trx_loop_amr_set(chan_state, 0);
|
||||
} else {
|
||||
/* enable AMR loop */
|
||||
trx_loop_amr_set(chan_state, 1);
|
||||
}
|
||||
if (ft < 0) {
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
|
||||
"Codec (FT = %d) of RTP frame not in list\n", ft_codec);
|
||||
goto free_bad_msg;
|
||||
}
|
||||
if (fn_is_codec_mode_request(fn) && chan_state->dl_ft != ft) {
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Codec (FT = %d) "
|
||||
" of RTP cannot be changed now, but in next frame\n", ft_codec);
|
||||
goto free_bad_msg;
|
||||
}
|
||||
chan_state->dl_ft = ft;
|
||||
if (bfi == AMR_BAD) {
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
|
||||
"Transmitting 'bad AMR frame'\n");
|
||||
goto free_bad_msg;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
inval_mode2:
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
|
||||
goto free_bad_msg;
|
||||
}
|
||||
if (len < 0) {
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n");
|
||||
goto free_bad_msg;
|
||||
}
|
||||
if (msgb_l2len(msg_tch) != len) {
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send payload with "
|
||||
"invalid length! (expecting %d, received %d)\n",
|
||||
len, msgb_l2len(msg_tch));
|
||||
free_bad_msg:
|
||||
/* free message */
|
||||
msgb_free(msg_tch);
|
||||
msg_tch = NULL;
|
||||
goto send_frame;
|
||||
}
|
||||
}
|
||||
|
||||
send_frame:
|
||||
*_msg_tch = msg_tch;
|
||||
*_msg_facch = msg_facch;
|
||||
}
|
||||
|
||||
/* obtain a to-be-transmitted TCH/F (Full Traffic Channel) burst */
|
||||
ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
|
||||
enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
|
||||
{
|
||||
struct msgb *msg_tch = NULL, *msg_facch = NULL;
|
||||
struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
|
||||
struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
|
||||
struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
|
||||
uint8_t tch_mode = chan_state->tch_mode;
|
||||
ubit_t *burst, **bursts_p = &chan_state->dl_bursts;
|
||||
static ubit_t bits[GSM_BURST_LEN];
|
||||
|
||||
/* send burst, if we already got a frame */
|
||||
if (bid > 0) {
|
||||
if (!*bursts_p)
|
||||
return NULL;
|
||||
goto send_burst;
|
||||
}
|
||||
|
||||
tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch);
|
||||
|
||||
/* BURST BYPASS */
|
||||
|
||||
/* allocate burst memory, if not already,
|
||||
* otherwise shift buffer by 4 bursts for interleaving */
|
||||
if (!*bursts_p) {
|
||||
*bursts_p = talloc_zero_size(tall_bts_ctx, 928);
|
||||
if (!*bursts_p)
|
||||
return NULL;
|
||||
} else {
|
||||
memcpy(*bursts_p, *bursts_p + 464, 464);
|
||||
memset(*bursts_p + 464, 0, 464);
|
||||
}
|
||||
|
||||
/* no message at all */
|
||||
if (!msg_tch && !msg_facch) {
|
||||
LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n");
|
||||
goto send_burst;
|
||||
}
|
||||
|
||||
/* encode bursts (prioritize FACCH) */
|
||||
if (msg_facch)
|
||||
gsm0503_tch_fr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch),
|
||||
1);
|
||||
else if (tch_mode == GSM48_CMODE_SPEECH_AMR)
|
||||
/* the first FN 4,13,21 defines that CMI is included in frame,
|
||||
* the first FN 0,8,17 defines that CMR is included in frame.
|
||||
*/
|
||||
gsm0503_tch_afs_encode(*bursts_p, msg_tch->l2h + 2,
|
||||
msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn),
|
||||
chan_state->codec, chan_state->codecs,
|
||||
chan_state->dl_ft,
|
||||
chan_state->dl_cmr);
|
||||
else
|
||||
gsm0503_tch_fr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch), 1);
|
||||
|
||||
/* free message */
|
||||
if (msg_tch)
|
||||
msgb_free(msg_tch);
|
||||
if (msg_facch)
|
||||
msgb_free(msg_facch);
|
||||
|
||||
send_burst:
|
||||
/* compose burst */
|
||||
burst = *bursts_p + bid * 116;
|
||||
memset(bits, 0, 3);
|
||||
memcpy(bits + 3, burst, 58);
|
||||
memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
|
||||
memcpy(bits + 87, burst + 58, 58);
|
||||
memset(bits + 145, 0, 3);
|
||||
|
||||
if (nbits)
|
||||
*nbits = GSM_BURST_LEN;
|
||||
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
|
||||
|
||||
return bits;
|
||||
}
|
|
@ -0,0 +1,408 @@
|
|||
/*
|
||||
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* 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 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 <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <osmocom/gsm/gsm0502.h>
|
||||
|
||||
#include <osmocom/codec/codec.h>
|
||||
#include <osmocom/codec/ecu.h>
|
||||
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
#include <osmocom/coding/gsm0503_amr_dtx.h>
|
||||
|
||||
#include <osmo-bts/bts.h>
|
||||
#include <osmo-bts/l1sap.h>
|
||||
#include <osmo-bts/logging.h>
|
||||
#include <osmo-bts/scheduler.h>
|
||||
#include <osmo-bts/scheduler_backend.h>
|
||||
#include <osmo-bts/msg_utils.h>
|
||||
|
||||
#include <sched_utils.h>
|
||||
#include <loops.h>
|
||||
|
||||
/*! \brief a single TCH/H burst was received by the PHY, process it */
|
||||
int rx_tchh_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
|
||||
uint8_t bid, const struct trx_ul_burst_ind *bi)
|
||||
{
|
||||
struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
|
||||
struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
|
||||
sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
|
||||
uint8_t *mask = &chan_state->ul_mask;
|
||||
uint8_t rsl_cmode = chan_state->rsl_cmode;
|
||||
uint8_t tch_mode = chan_state->tch_mode;
|
||||
uint8_t tch_data[128]; /* just to be safe */
|
||||
int rc, amr = 0;
|
||||
int n_errors = 0;
|
||||
int n_bits_total = 0;
|
||||
bool bfi_flag = false;
|
||||
struct gsm_lchan *lchan =
|
||||
get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | bi->tn);
|
||||
/* Note on FN-10: If we are at FN 10, we decoded an even aligned
|
||||
* TCH/FACCH frame, because our burst buffer carries 6 bursts.
|
||||
* Even FN ending at: 10,11,19,20,2,3
|
||||
*/
|
||||
int fn_is_odd = (((bi->fn + 26 - 10) % 26) >> 2) & 1;
|
||||
unsigned int fn_begin;
|
||||
uint16_t ber10k;
|
||||
uint8_t is_sub = 0;
|
||||
uint8_t ft;
|
||||
|
||||
/* If handover RACH detection is turned on, treat this burst as an Access Burst.
|
||||
* Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
|
||||
if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
|
||||
return rx_rach_fn(l1t, chan, bid, bi);
|
||||
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Received TCH/H, bid=%u\n", bid);
|
||||
|
||||
/* allocate burst memory, if not already */
|
||||
if (!*bursts_p) {
|
||||
*bursts_p = talloc_zero_size(tall_bts_ctx, 696);
|
||||
if (!*bursts_p)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* clear burst */
|
||||
if (bid == 0) {
|
||||
memset(*bursts_p + 464, 0, 232);
|
||||
*mask = 0x0;
|
||||
}
|
||||
|
||||
/* update mask */
|
||||
*mask |= (1 << bid);
|
||||
|
||||
/* copy burst to end of buffer of 6 bursts */
|
||||
burst = *bursts_p + bid * 116 + 464;
|
||||
if (bi->burst_len > 0) {
|
||||
memcpy(burst, bi->burst + 3, 58);
|
||||
memcpy(burst + 58, bi->burst + 87, 58);
|
||||
} else
|
||||
memset(burst, 0, 116);
|
||||
|
||||
/* wait until complete set of bursts */
|
||||
if (bid != 1)
|
||||
return 0;
|
||||
|
||||
/* check for complete set of bursts */
|
||||
if ((*mask & 0x3) != 0x3) {
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
|
||||
"Received incomplete frame (%u/%u)\n",
|
||||
bi->fn % l1ts->mf_period, l1ts->mf_period);
|
||||
}
|
||||
*mask = 0x0;
|
||||
|
||||
/* skip second of two TCH frames of FACCH was received */
|
||||
if (chan_state->ul_ongoing_facch) {
|
||||
chan_state->ul_ongoing_facch = 0;
|
||||
memcpy(*bursts_p, *bursts_p + 232, 232);
|
||||
memcpy(*bursts_p + 232, *bursts_p + 464, 232);
|
||||
ber10k = 0;
|
||||
goto bfi;
|
||||
}
|
||||
|
||||
/* decode
|
||||
* also shift buffer by 4 bursts for interleaving */
|
||||
switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1
|
||||
: tch_mode) {
|
||||
case GSM48_CMODE_SPEECH_V1: /* HR or signalling */
|
||||
/* Note on FN-10: If we are at FN 10, we decoded an even aligned
|
||||
* TCH/FACCH frame, because our burst buffer carries 6 bursts.
|
||||
* Even FN ending at: 10,11,19,20,2,3
|
||||
*/
|
||||
rc = gsm0503_tch_hr_decode(tch_data, *bursts_p,
|
||||
fn_is_odd, &n_errors, &n_bits_total);
|
||||
if (rc >= 0) /* DTXu */
|
||||
lchan_set_marker(osmo_hr_check_sid(tch_data, rc), lchan);
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
/* the first FN 0,8,17 or 1,9,18 defines that CMI is included
|
||||
* in frame, the first FN 4,13,21 or 5,14,22 defines that CMR
|
||||
* is included in frame.
|
||||
*/
|
||||
|
||||
/* See comment in function rx_tchf_fn() */
|
||||
switch (chan_state->amr_last_dtx) {
|
||||
case AHS_ONSET:
|
||||
case AHS_SID_FIRST_INH:
|
||||
case AHS_SID_UPDATE_INH:
|
||||
lchan_set_marker(false, lchan);
|
||||
break;
|
||||
}
|
||||
|
||||
/* See comment in function rx_tchf_fn() */
|
||||
amr = 2;
|
||||
rc = gsm0503_tch_ahs_decode_dtx(tch_data + amr, *bursts_p,
|
||||
fn_is_odd, fn_is_odd, chan_state->codec,
|
||||
chan_state->codecs, &chan_state->ul_ft,
|
||||
&chan_state->ul_cmr, &n_errors, &n_bits_total, &chan_state->amr_last_dtx);
|
||||
|
||||
/* Tag all frames that are not regular AMR voice frames
|
||||
as SUB-Frames */
|
||||
if (chan_state->amr_last_dtx != AMR_OTHER) {
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Received AMR SID frame: %s\n",
|
||||
gsm0503_amr_dtx_frame_name(chan_state->amr_last_dtx));
|
||||
is_sub = 1;
|
||||
}
|
||||
|
||||
/* See comment in function rx_tchf_fn() */
|
||||
switch (chan_state->amr_last_dtx) {
|
||||
case AHS_SID_FIRST_P1:
|
||||
case AHS_SID_FIRST_P2:
|
||||
case AHS_SID_UPDATE:
|
||||
case AHS_SID_UPDATE_CN:
|
||||
lchan_set_marker(true, lchan);
|
||||
lchan->rtp_tx_marker = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc)
|
||||
trx_loop_amr_input(l1t,
|
||||
trx_chan_desc[chan].chan_nr | bi->tn, chan_state,
|
||||
n_errors, n_bits_total);
|
||||
|
||||
/* only good speech frames get rtp header */
|
||||
if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
|
||||
if (chan_state->amr_last_dtx == AMR_OTHER) {
|
||||
ft = chan_state->codec[chan_state->ul_cmr];
|
||||
} else {
|
||||
/* SID frames will always get Frame Type Index 8 (AMR_SID) */
|
||||
ft = AMR_SID;
|
||||
}
|
||||
rc = osmo_amr_rtp_enc(tch_data,
|
||||
chan_state->codec[chan_state->ul_cmr],
|
||||
ft, AMR_GOOD);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
|
||||
"TCH mode %u invalid, please fix!\n",
|
||||
tch_mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
memcpy(*bursts_p, *bursts_p + 232, 232);
|
||||
memcpy(*bursts_p + 232, *bursts_p + 464, 232);
|
||||
ber10k = compute_ber10k(n_bits_total, n_errors);
|
||||
|
||||
|
||||
/* Check if the frame is bad */
|
||||
if (rc < 0) {
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
|
||||
"Received bad data (%u/%u)\n",
|
||||
bi->fn % l1ts->mf_period, l1ts->mf_period);
|
||||
bfi_flag = true;
|
||||
} else if (rc < 4) {
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
|
||||
"Received bad data (%u/%u) with invalid codec mode %d\n",
|
||||
bi->fn % l1ts->mf_period, l1ts->mf_period, rc);
|
||||
bfi_flag = true;
|
||||
}
|
||||
|
||||
if (rc != GSM_MACBLOCK_LEN && lchan->ecu_state)
|
||||
osmo_ecu_frame_in(lchan->ecu_state, bfi_flag, tch_data, rc);
|
||||
|
||||
if (bfi_flag)
|
||||
goto bfi;
|
||||
|
||||
/* FACCH */
|
||||
if (rc == GSM_MACBLOCK_LEN) {
|
||||
chan_state->ul_ongoing_facch = 1;
|
||||
uint16_t ber10k = compute_ber10k(n_bits_total, n_errors);
|
||||
if (lchan->nr == 0)
|
||||
fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_FACCH_H0);
|
||||
else
|
||||
fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_FACCH_H1);
|
||||
_sched_compose_ph_data_ind(l1t, bi->tn, fn_begin, chan,
|
||||
tch_data + amr, GSM_MACBLOCK_LEN,
|
||||
/* FIXME: AVG both RSSI and ToA */
|
||||
bi->rssi, bi->toa256,
|
||||
0 /* FIXME: AVG C/I */,
|
||||
ber10k, PRES_INFO_UNKNOWN);
|
||||
bfi:
|
||||
/* FIXME: a FACCH/H frame replaces two speech frames,
|
||||
* so we actually need to send two bad frame indications! */
|
||||
if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
|
||||
/* indicate bad frame */
|
||||
if (lchan->tch.dtx.ul_sid) {
|
||||
/* DTXu: pause in progress. Push empty payload to upper layers */
|
||||
rc = 0;
|
||||
goto compose_l1sap;
|
||||
}
|
||||
|
||||
/* If there is an ECU active on this channel, use its output */
|
||||
if (lchan->ecu_state) {
|
||||
rc = osmo_ecu_frame_out(lchan->ecu_state, tch_data);
|
||||
if (rc >= 0) /* Otherwise we send a BFI */
|
||||
goto compose_l1sap;
|
||||
}
|
||||
|
||||
switch (tch_mode) {
|
||||
case GSM48_CMODE_SPEECH_V1: /* HR */
|
||||
tch_data[0] = 0x70; /* F = 0, FT = 111 */
|
||||
memset(tch_data + 1, 0, 14);
|
||||
rc = 15;
|
||||
break;
|
||||
case GSM48_CMODE_SPEECH_AMR: /* AMR */
|
||||
rc = osmo_amr_rtp_enc(tch_data,
|
||||
chan_state->codec[chan_state->dl_cmr],
|
||||
chan_state->codec[chan_state->dl_ft],
|
||||
AMR_BAD);
|
||||
if (rc < 2) {
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
|
||||
"Failed to encode AMR_BAD frame (rc=%d), "
|
||||
"not sending BFI\n", rc);
|
||||
return -EINVAL;
|
||||
}
|
||||
memset(tch_data + 2, 0, rc - 2);
|
||||
break;
|
||||
default:
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
|
||||
"TCH mode %u invalid, please fix!\n", tch_mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
|
||||
return 0;
|
||||
|
||||
compose_l1sap:
|
||||
/* TCH or BFI */
|
||||
/* Note on FN 19 or 20: If we received the last burst of a frame,
|
||||
* it actually starts at FN 8 or 9. A burst starting there, overlaps
|
||||
* with the slot 12, so an extra FN must be subtracted to get correct
|
||||
* start of frame.
|
||||
*/
|
||||
if (lchan->nr == 0)
|
||||
fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_TCH_H0);
|
||||
else
|
||||
fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_TCH_H1);
|
||||
return _sched_compose_tch_ind(l1t, bi->tn, fn_begin, chan,
|
||||
tch_data, rc, bi->toa256, ber10k, bi->rssi, is_sub);
|
||||
}
|
||||
|
||||
/* common section for generation of TCH bursts (TCH/H and TCH/F).
|
||||
* FIXME: this function is over-complicated, refactor / get rid of it. */
|
||||
extern void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
|
||||
enum trx_chan_type chan, uint8_t bid,
|
||||
struct msgb **_msg_tch, struct msgb **_msg_facch);
|
||||
|
||||
/* obtain a to-be-transmitted TCH/H (Half Traffic Channel) burst */
|
||||
ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
|
||||
enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
|
||||
{
|
||||
struct msgb *msg_tch = NULL, *msg_facch = NULL;
|
||||
struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
|
||||
struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
|
||||
struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
|
||||
uint8_t tch_mode = chan_state->tch_mode;
|
||||
ubit_t *burst, **bursts_p = &chan_state->dl_bursts;
|
||||
static ubit_t bits[GSM_BURST_LEN];
|
||||
|
||||
/* send burst, if we already got a frame */
|
||||
if (bid > 0) {
|
||||
if (!*bursts_p)
|
||||
return NULL;
|
||||
goto send_burst;
|
||||
}
|
||||
|
||||
/* get TCH and/or FACCH */
|
||||
tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch);
|
||||
|
||||
/* check for FACCH alignment */
|
||||
if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) {
|
||||
LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on "
|
||||
"even frames, please fix RTS!\n");
|
||||
msgb_free(msg_facch);
|
||||
msg_facch = NULL;
|
||||
}
|
||||
|
||||
/* BURST BYPASS */
|
||||
|
||||
/* allocate burst memory, if not already,
|
||||
* otherwise shift buffer by 2 bursts for interleaving */
|
||||
if (!*bursts_p) {
|
||||
*bursts_p = talloc_zero_size(tall_bts_ctx, 696);
|
||||
if (!*bursts_p)
|
||||
return NULL;
|
||||
} else {
|
||||
memcpy(*bursts_p, *bursts_p + 232, 232);
|
||||
if (chan_state->dl_ongoing_facch) {
|
||||
memcpy(*bursts_p + 232, *bursts_p + 464, 232);
|
||||
memset(*bursts_p + 464, 0, 232);
|
||||
} else {
|
||||
memset(*bursts_p + 232, 0, 232);
|
||||
}
|
||||
}
|
||||
|
||||
/* no message at all */
|
||||
if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) {
|
||||
LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n");
|
||||
goto send_burst;
|
||||
}
|
||||
|
||||
/* encode bursts (prioritize FACCH) */
|
||||
if (msg_facch) {
|
||||
gsm0503_tch_hr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch));
|
||||
chan_state->dl_ongoing_facch = 1; /* first of two TCH frames */
|
||||
} else if (chan_state->dl_ongoing_facch) /* second of two TCH frames */
|
||||
chan_state->dl_ongoing_facch = 0; /* we are done with FACCH */
|
||||
else if (tch_mode == GSM48_CMODE_SPEECH_AMR)
|
||||
/* the first FN 4,13,21 or 5,14,22 defines that CMI is included
|
||||
* in frame, the first FN 0,8,17 or 1,9,18 defines that CMR is
|
||||
* included in frame. */
|
||||
gsm0503_tch_ahs_encode(*bursts_p, msg_tch->l2h + 2,
|
||||
msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn),
|
||||
chan_state->codec, chan_state->codecs,
|
||||
chan_state->dl_ft,
|
||||
chan_state->dl_cmr);
|
||||
else
|
||||
gsm0503_tch_hr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch));
|
||||
|
||||
/* free message */
|
||||
if (msg_tch)
|
||||
msgb_free(msg_tch);
|
||||
if (msg_facch)
|
||||
msgb_free(msg_facch);
|
||||
|
||||
send_burst:
|
||||
/* compose burst */
|
||||
burst = *bursts_p + bid * 116;
|
||||
memset(bits, 0, 3);
|
||||
memcpy(bits + 3, burst, 58);
|
||||
memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
|
||||
memcpy(bits + 87, burst + 58, 58);
|
||||
memset(bits + 145, 0, 3);
|
||||
|
||||
if (nbits)
|
||||
*nbits = GSM_BURST_LEN;
|
||||
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
|
||||
|
||||
return bits;
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* 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 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 <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/bits.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/coding/gsm0503_coding.h>
|
||||
|
||||
#include <osmo-bts/bts.h>
|
||||
#include <osmo-bts/l1sap.h>
|
||||
#include <osmo-bts/logging.h>
|
||||
#include <osmo-bts/scheduler.h>
|
||||
#include <osmo-bts/scheduler_backend.h>
|
||||
|
||||
#include <sched_utils.h>
|
||||
|
||||
/*! \brief a single (SDCCH/SACCH) burst was received by the PHY, process it */
|
||||
int rx_data_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
|
||||
uint8_t bid, const struct trx_ul_burst_ind *bi)
|
||||
{
|
||||
struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
|
||||
struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
|
||||
sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
|
||||
uint32_t *first_fn = &chan_state->ul_first_fn;
|
||||
uint8_t *mask = &chan_state->ul_mask;
|
||||
float *rssi_sum = &chan_state->rssi_sum;
|
||||
uint8_t *rssi_num = &chan_state->rssi_num;
|
||||
int32_t *toa256_sum = &chan_state->toa256_sum;
|
||||
uint8_t *toa_num = &chan_state->toa_num;
|
||||
int32_t *ci_cb_sum = &chan_state->ci_cb_sum;
|
||||
uint8_t *ci_cb_num = &chan_state->ci_cb_num;
|
||||
uint8_t l2[GSM_MACBLOCK_LEN], l2_len;
|
||||
int n_errors = 0;
|
||||
int n_bits_total = 0;
|
||||
int16_t lqual_cb;
|
||||
uint16_t ber10k;
|
||||
int rc;
|
||||
|
||||
/* If handover RACH detection is turned on, treat this burst as an Access Burst.
|
||||
* Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
|
||||
if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
|
||||
return rx_rach_fn(l1t, chan, bid, bi);
|
||||
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
|
||||
"Received Data, bid=%u\n", bid);
|
||||
|
||||
/* allocate burst memory, if not already */
|
||||
if (!*bursts_p) {
|
||||
*bursts_p = talloc_zero_size(tall_bts_ctx, 464);
|
||||
if (!*bursts_p)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* clear burst & store frame number of first burst */
|
||||
if (bid == 0) {
|
||||
memset(*bursts_p, 0, 464);
|
||||
*mask = 0x0;
|
||||
*first_fn = bi->fn;
|
||||
*rssi_sum = 0;
|
||||
*rssi_num = 0;
|
||||
*toa256_sum = 0;
|
||||
*toa_num = 0;
|
||||
*ci_cb_sum = 0;
|
||||
*ci_cb_num = 0;
|
||||
}
|
||||
|
||||
/* update mask + RSSI */
|
||||
*mask |= (1 << bid);
|
||||
*rssi_sum += bi->rssi;
|
||||
(*rssi_num)++;
|
||||
*toa256_sum += bi->toa256;
|
||||
(*toa_num)++;
|
||||
|
||||
/* C/I: Carrier-to-Interference ratio (in centiBels) */
|
||||
if (bi->flags & TRX_BI_F_CI_CB) {
|
||||
*ci_cb_sum += bi->ci_cb;
|
||||
(*ci_cb_num)++;
|
||||
}
|
||||
|
||||
/* Copy burst to buffer of 4 bursts. If the burst indication contains
|
||||
* no data, ensure that the buffer does not stay uninitialized */
|
||||
burst = *bursts_p + bid * 116;
|
||||
if (bi->burst_len > 0) {
|
||||
memcpy(burst, bi->burst + 3, 58);
|
||||
memcpy(burst + 58, bi->burst + 87, 58);
|
||||
} else
|
||||
memset(burst, 0, 58 * 2);
|
||||
|
||||
/* wait until complete set of bursts */
|
||||
if (bid != 3)
|
||||
return 0;
|
||||
|
||||
/* check for complete set of bursts */
|
||||
if ((*mask & 0xf) != 0xf) {
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
|
||||
"Received incomplete data (%u/%u)\n",
|
||||
bi->fn % l1ts->mf_period, l1ts->mf_period);
|
||||
|
||||
/* we require first burst to have correct FN */
|
||||
if (!(*mask & 0x1)) {
|
||||
*mask = 0x0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
*mask = 0x0;
|
||||
|
||||
/* decode */
|
||||
rc = gsm0503_xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total);
|
||||
if (rc) {
|
||||
LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
|
||||
"Received bad data (%u/%u)\n",
|
||||
bi->fn % l1ts->mf_period, l1ts->mf_period);
|
||||
l2_len = 0;
|
||||
} else
|
||||
l2_len = GSM_MACBLOCK_LEN;
|
||||
|
||||
lqual_cb = *ci_cb_num ? (*ci_cb_sum / *ci_cb_num) : 0;
|
||||
ber10k = compute_ber10k(n_bits_total, n_errors);
|
||||
return _sched_compose_ph_data_ind(l1t, bi->tn, *first_fn,
|
||||
chan, l2, l2_len,
|
||||
*rssi_sum / *rssi_num,
|
||||
*toa256_sum / *toa_num,
|
||||
lqual_cb, ber10k,
|
||||
PRES_INFO_UNKNOWN);
|
||||
}
|
||||
|
||||
/* obtain a to-be-transmitted xCCH (e.g SACCH or SDCCH) burst */
|
||||
ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
|
||||
enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
|
||||
{
|
||||
struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
|
||||
struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
|
||||
struct msgb *msg = NULL; /* make GCC happy */
|
||||
ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts;
|
||||
static ubit_t bits[GSM_BURST_LEN];
|
||||
|
||||
/* send burst, if we already got a frame */
|
||||
if (bid > 0) {
|
||||
if (!*bursts_p)
|
||||
return NULL;
|
||||
goto send_burst;
|
||||
}
|
||||
|
||||
/* get mac block from queue */
|
||||
msg = _sched_dequeue_prim(l1t, tn, fn, chan);
|
||||
if (msg)
|
||||
goto got_msg;
|
||||
|
||||
LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n");
|
||||
|
||||
no_msg:
|
||||
/* free burst memory */
|
||||
if (*bursts_p) {
|
||||
talloc_free(*bursts_p);
|
||||
*bursts_p = NULL;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
got_msg:
|
||||
/* check validity of message */
|
||||
if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) {
|
||||
LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! "
|
||||
"(len=%d)\n", msgb_l2len(msg));
|
||||
/* free message */
|
||||
msgb_free(msg);
|
||||
goto no_msg;
|
||||
}
|
||||
|
||||
/* BURST BYPASS */
|
||||
|
||||
/* handle loss detection of SACCH */
|
||||
if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) {
|
||||
/* count and send BFI */
|
||||
if (++(l1ts->chan_state[chan].lost_frames) > 1) {
|
||||
/* TODO: Should we pass old TOA here? Otherwise we risk
|
||||
* unnecessary decreasing TA */
|
||||
|
||||
/* Note: RSSI is set to 0 to indicate to the higher
|
||||
* layers that this is a faked ph_data_ind */
|
||||
_sched_compose_ph_data_ind(l1t, tn, 0, chan, NULL, 0,
|
||||
0, 0, 0, 10000,
|
||||
PRES_INFO_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
/* allocate burst memory, if not already */
|
||||
if (!*bursts_p) {
|
||||
*bursts_p = talloc_zero_size(tall_bts_ctx, 464);
|
||||
if (!*bursts_p)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* encode bursts */
|
||||
gsm0503_xcch_encode(*bursts_p, msg->l2h);
|
||||
|
||||
/* free message */
|
||||
msgb_free(msg);
|
||||
|
||||
send_burst:
|
||||
/* compose burst */
|
||||
burst = *bursts_p + bid * 116;
|
||||
memset(bits, 0, 3);
|
||||
memcpy(bits + 3, burst, 58);
|
||||
memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
|
||||
memcpy(bits + 87, burst + 58, 58);
|
||||
memset(bits + 145, 0, 3);
|
||||
|
||||
if (nbits)
|
||||
*nbits = GSM_BURST_LEN;
|
||||
|
||||
LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
|
||||
|
||||
return bits;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/* Auxiliary scheduler utilities.
|
||||
*
|
||||
* (C) 2017 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* 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 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
extern void *tall_bts_ctx;
|
||||
|
||||
/* Compute the bit error rate in 1/10000 units */
|
||||
static inline uint16_t compute_ber10k(int n_bits_total, int n_errors)
|
||||
{
|
||||
if (n_bits_total == 0)
|
||||
return 10000;
|
||||
else
|
||||
return 10000 * n_errors / n_bits_total;
|
||||
}
|
||||
|
||||
/* determine if the FN is transmitting a CMR (1) or not (0) */
|
||||
static inline int fn_is_codec_mode_request(uint32_t fn)
|
||||
{
|
||||
return (((fn + 4) % 26) >> 2) & 1;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue