diff options
Diffstat (limited to 'src/common/scheduler.c')
-rw-r--r-- | src/common/scheduler.c | 1031 |
1 files changed, 635 insertions, 396 deletions
diff --git a/src/common/scheduler.c b/src/common/scheduler.c index 95a1b00e..a449d167 100644 --- a/src/common/scheduler.c +++ b/src/common/scheduler.c @@ -3,6 +3,7 @@ /* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> * (C) 2015 by Harald Welte <laforge@gnumonks.org> + * Contributions by sysmocom - s.f.m.c. GmbH * * All Rights Reserved * @@ -14,7 +15,7 @@ * 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. + * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. @@ -29,8 +30,12 @@ #include <osmocom/core/msgb.h> #include <osmocom/core/talloc.h> #include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stats.h> #include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/gsm0502.h> #include <osmocom/gsm/a5.h> #include <osmo-bts/gsm_data.h> @@ -39,17 +44,16 @@ #include <osmo-bts/l1sap.h> #include <osmo-bts/scheduler.h> #include <osmo-bts/scheduler_backend.h> +#include <osmo-bts/bts.h> extern void *tall_bts_ctx; -static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan); -static int rts_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan); -static int rts_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan); +static int rts_data_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br); +static int rts_tchf_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br); +static int rts_tchh_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br); + /*! \brief Dummy Burst (TS 05.02 Chapter 5.2.6) */ -static const ubit_t dummy_burst[GSM_BURST_LEN] = { +const ubit_t _sched_dummy_burst[GSM_BURST_LEN] = { 0,0,0, 1,1,1,1,1,0,1,1,0,1,1,1,0,1,1,0,0,0,0,0,1,0,1,0,0,1,0,0,1,1,1,0, 0,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,1,1,1,0,0, @@ -59,28 +63,51 @@ static const ubit_t dummy_burst[GSM_BURST_LEN] = { 0,0,0, }; -/*! \brief FCCH Burst (TS 05.02 Chapter 5.2.4) */ -const ubit_t _sched_fcch_burst[GSM_BURST_LEN] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -}; - -/*! \brief Training Sequences (TS 05.02 Chapter 5.2.3) */ -const ubit_t _sched_tsc[8][26] = { - { 0,0,1,0,0,1,0,1,1,1,0,0,0,0,1,0,0,0,1,0,0,1,0,1,1,1, }, - { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,1,0,0,0,1,0,1,1,0,1,1,1, }, - { 0,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,0,0,0,1,1,1,0, }, - { 0,1,0,0,0,1,1,1,1,0,1,1,0,1,0,0,0,1,0,0,0,1,1,1,1,0, }, - { 0,0,0,1,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,1,0,1,0,1,1, }, - { 0,1,0,0,1,1,1,0,1,0,1,1,0,0,0,0,0,1,0,0,1,1,1,0,1,0, }, - { 1,0,1,0,0,1,1,1,1,1,0,1,1,0,0,0,1,0,1,0,0,1,1,1,1,1, }, - { 1,1,1,0,1,1,1,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,1,1,0,0, }, +/*! Training Sequences for Normal Burst (see 3GPP TS 45.002, section 5.2.3) */ +const ubit_t _sched_train_seq_gmsk_nb[4][8][26] = { + { /* TSC set 1, table 5.2.3a */ + { 0,0,1,0,0,1,0,1,1,1,0,0,0,0,1,0,0,0,1,0,0,1,0,1,1,1 }, + { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,1,0,0,0,1,0,1,1,0,1,1,1 }, + { 0,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,0,0,0,1,1,1,0 }, + { 0,1,0,0,0,1,1,1,1,0,1,1,0,1,0,0,0,1,0,0,0,1,1,1,1,0 }, + { 0,0,0,1,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,1,0,1,0,1,1 }, + { 0,1,0,0,1,1,1,0,1,0,1,1,0,0,0,0,0,1,0,0,1,1,1,0,1,0 }, + { 1,0,1,0,0,1,1,1,1,1,0,1,1,0,0,0,1,0,1,0,0,1,1,1,1,1 }, + { 1,1,1,0,1,1,1,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,1,1,0,0 }, + }, + { /* TSC set 2, table 5.2.3b */ + { 0,1,1,0,0,0,1,0,0,0,1,0,0,1,0,0,1,1,1,1,0,1,0,1,1,1 }, + { 0,1,0,1,1,1,1,0,1,0,0,1,1,0,1,1,1,0,1,1,1,0,0,0,0,1 }, + { 0,1,0,0,0,0,0,1,0,1,1,0,0,0,1,1,1,0,1,1,1,0,1,1,0,0 }, + { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,0,0,1,1,1,1,0,1,0,0,0,0 }, + { 0,1,1,1,0,1,0,0,1,1,1,1,0,1,0,0,1,1,1,0,1,1,1,1,1,0 }, + { 0,1,0,0,0,0,0,1,0,0,1,1,0,1,0,1,0,0,1,1,1,1,0,0,1,1 }, + { 0,0,0,1,0,0,0,0,1,1,0,1,0,0,0,0,1,1,0,1,1,1,0,1,0,1 }, + { 0,1,0,0,0,1,0,1,1,1,0,0,1,1,1,1,1,1,0,0,1,0,1,0,0,1 }, + }, + { /* TSC set 3, table 5.2.3c */ + { 1,1,0,0,0,0,1,0,0,1,0,0,0,1,1,1,1,0,1,0,1,0,0,0,1,0 }, + { 0,0,1,0,1,1,1,1,1,0,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0,0 }, + { 1,1,0,0,1,0,0,0,1,1,1,1,1,0,1,1,1,0,1,0,1,1,0,1,1,0 }, + { 0,0,1,1,0,0,0,0,1,0,1,0,0,1,1,0,0,0,0,0,1,0,1,1,0,0 }, + { 0,0,0,1,1,1,1,0,1,0,1,1,1,0,1,0,0,0,0,1,0,0,0,1,1,0 }, + { 1,1,0,0,1,1,1,1,0,1,0,1,0,1,1,1,1,0,0,1,0,0,0,0,0,0 }, + { 1,0,1,1,1,0,0,1,1,0,1,0,1,1,1,1,1,1,0,0,0,1,0,0,0,0 }, + { 1,1,1,0,0,1,0,1,1,1,1,0,1,1,1,0,0,0,0,0,1,0,0,1,0,0 }, + }, + { /* TSC set 4, table 5.2.3d */ + { 1,1,0,0,1,1,1,0,1,0,0,0,0,0,1,0,0,0,1,1,0,1,0,0,0,0 }, + { 0,1,1,0,0,0,1,0,0,0,0,1,0,1,0,0,0,1,0,1,1,1,0,0,0,0 }, + { 1,1,1,0,0,1,0,0,0,0,0,1,0,1,0,1,0,0,1,1,1,0,0,0,0,0 }, + { 0,1,1,0,1,1,0,0,1,1,1,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0 }, + { 1,1,0,1,1,0,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0 }, + { 1,1,0,1,0,0,1,1,1,1,1,1,1,0,1,0,0,0,1,1,0,1,0,1,1,0 }, + { 0,0,1,0,0,1,1,1,1,1,1,1,0,0,1,0,1,0,1,0,1,1,0,0,0,0 }, + { 0,1,0,1,1,1,0,0,0,0,0,0,1,0,1,0,0,1,1,0,0,0,1,1,1,0 }, + }, }; -const ubit_t _sched_egprs_tsc[8][78] = { +const ubit_t _sched_train_seq_8psk_nb[8][78] = { { 1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0, 1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1, 1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1, }, @@ -108,7 +135,7 @@ const ubit_t _sched_egprs_tsc[8][78] = { }; /*! \brief SCH training sequence (TS 05.02 Chapter 5.2.5) */ -const ubit_t _sched_sch_train[64] = { +const ubit_t _sched_train_seq_gmsk_sb[64] = { 1,0,1,1,1,0,0,1,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,1,1, 0,0,1,0,1,1,0,1,0,1,0,0,0,1,0,1,0,1,1,1,0,1,1,0,0,0,0,1,1,0,1,1, }; @@ -118,18 +145,12 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { [TRXC_IDLE] = { .name = "IDLE", .desc = "Idle channel", - - /* On C0, BTS needs to ensure discontinuous burst transmission. - * Therefore we need to send dummy bursts on IDLE slots. */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, - .dl_fn = tx_idle_fn, }, [TRXC_FCCH] = { .name = "FCCH", /* 3GPP TS 05.02, section 3.3.2.1 */ .desc = "Frequency correction channel", /* Tx only, frequency correction bursts */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .dl_fn = tx_fcch_fn, }, [TRXC_SCH] = { @@ -137,7 +158,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { .desc = "Synchronization channel", /* Tx only, synchronization bursts */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .dl_fn = tx_sch_fn, }, [TRXC_BCCH] = { @@ -148,7 +168,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { /* Tx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4), * regular interleaving (3GPP TS 05.02, clause 7, table 3): * a L2 frame is interleaved over 4 consecutive bursts. */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .rts_fn = rts_data_fn, .dl_fn = tx_data_fn, }, @@ -158,7 +177,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { .chan_nr = RSL_CHAN_RACH, /* Rx only, RACH convolutional coding (3GPP TS 05.03, section 4.6). */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .ul_fn = rx_rach_fn, }, [TRXC_CCCH] = { @@ -169,7 +187,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { /* Tx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4), * regular interleaving (3GPP TS 05.02, clause 7, table 3): * a L2 frame is interleaved over 4 consecutive bursts. */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .rts_fn = rts_data_fn, .dl_fn = tx_data_fn, }, @@ -200,13 +217,14 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { /* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03, * chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7): * - * - a traffic frame is interleaved over 6 consecutive bursts + * - a traffic frame is interleaved over 4 consecutive bursts * using the even numbered bits of the first 2 bursts, - * all bits of the middle two 2 bursts, * and odd numbered bits of the last 2 bursts; * - a FACCH/H frame 'steals' (replaces) two traffic frames, - * interleaving is done over 4 consecutive bursts, - * the same as given for a TCH/FS. */ + * interleaving is done over 6 consecutive bursts, + * using the even numbered bits of the first 2 bursts, + * all bits of the middle two 2 bursts, + * and odd numbered bits of the last 2 bursts. */ .rts_fn = rts_tchh_fn, .dl_fn = tx_tchh_fn, .ul_fn = rx_tchh_fn, @@ -525,10 +543,7 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { .chan_nr = RSL_CHAN_OSMO_PDCH, /* Rx and Tx, multiple coding schemes: CS-2..4 and MCS-1..9 (3GPP TS - * 05.03, chapter 5), regular interleaving as specified for xCCH. - * NOTE: the burst buffer is three times bigger because the - * payload of EDGE bursts is three times longer. */ - .flags = TRX_CHAN_FLAG_PDCH, + * 05.03, chapter 5), regular interleaving as specified for xCCH. */ .rts_fn = rts_data_fn, .dl_fn = tx_pdtch_fn, .ul_fn = rx_pdtch_fn, @@ -538,11 +553,14 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { .desc = "Packet Timing advance control channel", .chan_nr = RSL_CHAN_OSMO_PDCH, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .flags = TRX_CHAN_FLAG_PDCH, + /* On the Uplink, mobile stations transmit random Access Bursts + * to allow estimation of the timing advance for one MS in packet + * transfer mode. On Downlink, the network sends timing advance + * updates for several mobile stations. The coding scheme used + * for PTCCH/D messages is the same as for PDTCH CS-1. */ .rts_fn = rts_data_fn, - .dl_fn = tx_data_fn, - .ul_fn = rx_data_fn, + .dl_fn = tx_pdtch_fn, + .ul_fn = rx_rach_fn, }, [TRXC_CBCH] = { /* TODO: distinguish CBCH on SDCCH/4 and SDCCH/8 */ @@ -551,163 +569,203 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { .chan_nr = RSL_CHAN_OSMO_CBCH4, /* Tx only, same as for TRXC_BCCH (xCCH), see above. */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .rts_fn = rts_data_fn, .dl_fn = tx_data_fn, }, }; +enum { + L1SCHED_TS_CTR_DL_LATE, + L1SCHED_TS_CTR_DL_NOT_FOUND, +}; + +static const struct rate_ctr_desc l1sched_ts_ctr_desc[] = { + [L1SCHED_TS_CTR_DL_LATE] = {"l1sched_ts:dl_late", "Downlink frames arrived too late to submit to lower layers"}, + [L1SCHED_TS_CTR_DL_NOT_FOUND] = {"l1sched_ts:dl_not_found", "Downlink frames not found while scheduling"}, +}; +static const struct rate_ctr_group_desc l1sched_ts_ctrg_desc = { + "l1sched_ts", + "L1 scheduler timeslot", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(l1sched_ts_ctr_desc), + l1sched_ts_ctr_desc +}; + /* * init / exit */ -int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx) +static void trx_sched_init_ts(struct gsm_bts_trx_ts *ts, + const unsigned int rate_ctr_idx) { - uint8_t tn; + struct l1sched_ts *l1ts; unsigned int i; + char name[128]; - if (!trx) - return -EINVAL; + l1ts = talloc_zero(ts->trx, struct l1sched_ts); + OSMO_ASSERT(l1ts != NULL); - l1t->trx = trx; + /* Link both structures */ + ts->priv = l1ts; + l1ts->ts = ts; - LOGP(DL1C, LOGL_NOTICE, "Init scheduler for trx=%u\n", l1t->trx->nr); + l1ts->ctrs = rate_ctr_group_alloc(ts->trx, + &l1sched_ts_ctrg_desc, + rate_ctr_idx); + snprintf(name, sizeof(name), "bts%u-trx%u-ts%u%s", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + ts->vamos.is_shadow ? "-shadow" : ""); + rate_ctr_group_set_name(l1ts->ctrs, name); - for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + INIT_LLIST_HEAD(&l1ts->dl_prims); - l1ts->mf_index = 0; - INIT_LLIST_HEAD(&l1ts->dl_prims); - for (i = 0; i < ARRAY_SIZE(l1ts->chan_state); i++) { - struct l1sched_chan_state *chan_state; - chan_state = &l1ts->chan_state[i]; - chan_state->active = 0; - } + for (i = 0; i < ARRAY_SIZE(l1ts->chan_state); i++) { + struct l1sched_chan_state *chan_state; + chan_state = &l1ts->chan_state[i]; + chan_state->active = false; } - - return 0; } -void trx_sched_exit(struct l1sched_trx *l1t) +void trx_sched_init(struct gsm_bts_trx *trx) { - struct gsm_bts_trx_ts *ts; - uint8_t tn; - int i; + unsigned int tn; - LOGP(DL1C, LOGL_NOTICE, "Exit scheduler for trx=%u\n", l1t->trx->nr); - - for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - msgb_queue_flush(&l1ts->dl_prims); - for (i = 0; i < _TRX_CHAN_MAX; i++) { - struct l1sched_chan_state *chan_state; - chan_state = &l1ts->chan_state[i]; - if (chan_state->dl_bursts) { - talloc_free(chan_state->dl_bursts); - chan_state->dl_bursts = NULL; - } - if (chan_state->ul_bursts) { - talloc_free(chan_state->ul_bursts); - chan_state->ul_bursts = NULL; - } - } - /* clear lchan channel states */ - ts = &l1t->trx->ts[tn]; - for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) - lchan_set_state(&ts->lchan[i], LCHAN_S_NONE); + OSMO_ASSERT(trx != NULL); + + LOGPTRX(trx, DL1C, LOGL_DEBUG, "Init scheduler structures\n"); + + /* Allocate shadow timeslots */ + gsm_bts_trx_init_shadow_ts(trx); + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + unsigned int rate_ctr_idx = trx->nr * 100 + tn; + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + + /* Init primary and shadow timeslots */ + trx_sched_init_ts(ts, rate_ctr_idx); + trx_sched_init_ts(ts->vamos.peer, rate_ctr_idx + 10); } } -/* close all logical channels and reset timeslots */ -void trx_sched_reset(struct l1sched_trx *l1t) +static void trx_sched_clean_ts(struct gsm_bts_trx_ts *ts) { - trx_sched_exit(l1t); - trx_sched_init(l1t, l1t->trx); + struct l1sched_ts *l1ts = ts->priv; + unsigned int i; + + msgb_queue_free(&l1ts->dl_prims); + rate_ctr_group_free(l1ts->ctrs); + l1ts->ctrs = NULL; + + /* clear lchan channel states */ + for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) + lchan_set_state(&ts->lchan[i], LCHAN_S_NONE); + + talloc_free(l1ts); + ts->priv = NULL; +} + +void trx_sched_clean(struct gsm_bts_trx *trx) +{ + unsigned int tn; + + LOGPTRX(trx, DL1C, LOGL_DEBUG, "Clean scheduler structures\n"); + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + + /* Clean primary and shadow timeslots */ + trx_sched_clean_ts(ts); + trx_sched_clean_ts(ts->vamos.peer); + } + + /* Free previously allocated shadow timeslots */ + gsm_bts_trx_free_shadow_ts(trx); } -struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn, - enum trx_chan_type chan) +struct msgb *_sched_dequeue_prim(struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br) { struct msgb *msg, *msg2; - struct osmo_phsap_prim *l1sap; - uint32_t prim_fn; + uint32_t prim_fn, l1sap_fn; uint8_t chan_nr, link_id; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); /* get prim of current fn from queue */ llist_for_each_entry_safe(msg, msg2, &l1ts->dl_prims, list) { - l1sap = msgb_l1sap_prim(msg); - if (l1sap->oph.operation != PRIM_OP_REQUEST) { -wrong_type: - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Prim has wrong type.\n"); -free_msg: - /* unlink and free message */ - llist_del(&msg->list); - msgb_free(msg); - return NULL; - } + struct osmo_phsap_prim *l1sap = msgb_l1sap_prim(msg); switch (l1sap->oph.primitive) { case PRIM_PH_DATA: chan_nr = l1sap->u.data.chan_nr; link_id = l1sap->u.data.link_id; - prim_fn = ((l1sap->u.data.fn + GSM_HYPERFRAME - fn) % GSM_HYPERFRAME); + l1sap_fn = l1sap->u.data.fn; break; case PRIM_TCH: chan_nr = l1sap->u.tch.chan_nr; link_id = 0; - prim_fn = ((l1sap->u.tch.fn + GSM_HYPERFRAME - fn) % GSM_HYPERFRAME); + l1sap_fn = l1sap->u.tch.fn; break; default: - goto wrong_type; + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Prim has wrong type.\n"); + goto free_msg; } - if (prim_fn > 100) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, - "Prim %u is out of range (100), or channel %s with " + prim_fn = GSM_TDMA_FN_SUB(l1sap_fn, br->fn); + if (prim_fn > 100) { /* l1sap_fn < fn */ + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, + "Prim %u is out of range (%u vs exp %u), or channel %s with " "type %s is already disabled. If this happens in " "conjunction with PCU, increase 'rts-advance' by 5.\n", - prim_fn, get_lchan_by_chan_nr(l1t->trx, chan_nr)->name, - trx_chan_desc[chan].name); + prim_fn, l1sap_fn, br->fn, + get_lchan_by_chan_nr(l1ts->ts->trx, chan_nr)->name, + trx_chan_desc[br->chan].name); + rate_ctr_inc2(l1ts->ctrs, L1SCHED_TS_CTR_DL_LATE); /* unlink and free message */ llist_del(&msg->list); msgb_free(msg); continue; } - if (prim_fn > 0) - continue; + if (prim_fn > 0) /* l1sap_fn > fn */ + break; - goto found_msg; + /* l1sap_fn == fn */ + if ((chan_nr ^ (trx_chan_desc[br->chan].chan_nr | br->tn)) + || ((link_id & 0xc0) ^ trx_chan_desc[br->chan].link_id)) { + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Prim has wrong chan_nr=0x%02x link_id=%02x, " + "expecting chan_nr=0x%02x link_id=%02x.\n", chan_nr, link_id, + trx_chan_desc[br->chan].chan_nr | br->tn, trx_chan_desc[br->chan].link_id); + goto free_msg; + } + + /* unlink and return message */ + llist_del(&msg->list); + return msg; } + /* Queue was traversed with no candidate, no prim is available for current FN: */ + rate_ctr_inc2(l1ts->ctrs, L1SCHED_TS_CTR_DL_NOT_FOUND); return NULL; -found_msg: - if ((chan_nr ^ (trx_chan_desc[chan].chan_nr | tn)) - || ((link_id & 0xc0) ^ trx_chan_desc[chan].link_id)) { - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Prim has wrong chan_nr=0x%02x link_id=%02x, " - "expecting chan_nr=0x%02x link_id=%02x.\n", chan_nr, link_id, - trx_chan_desc[chan].chan_nr | tn, trx_chan_desc[chan].link_id); - goto free_msg; - } - - /* unlink and return message */ +free_msg: + /* unlink and free message */ llist_del(&msg->list); - return msg; + msgb_free(msg); + return NULL; } -int _sched_compose_ph_data_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t *l2, - uint8_t l2_len, float rssi, +int _sched_compose_ph_data_ind(struct l1sched_ts *l1ts, uint32_t fn, + enum trx_chan_type chan, + const uint8_t *data, size_t data_len, + uint16_t ber10k, float rssi, int16_t ta_offs_256bits, int16_t link_qual_cb, - uint16_t ber10k, enum osmo_ph_pres_info_type presence_info) { struct msgb *msg; struct osmo_phsap_prim *l1sap; - uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + uint8_t chan_nr = trx_chan_desc[chan].chan_nr | l1ts->ts->nr; + + /* VAMOS: use Osmocom specific channel number */ + if (l1ts->ts->vamos.is_shadow) + chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK; /* compose primitive */ - msg = l1sap_msgb_alloc(l2_len); + msg = l1sap_msgb_alloc(data_len); l1sap = msgb_l1sap_prim(msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_INDICATION, msg); @@ -719,48 +777,53 @@ int _sched_compose_ph_data_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, l1sap->u.data.ta_offs_256bits = ta_offs_256bits; l1sap->u.data.lqual_cb = link_qual_cb; l1sap->u.data.pdch_presence_info = presence_info; - msg->l2h = msgb_put(msg, l2_len); - if (l2_len) - memcpy(msg->l2h, l2, l2_len); - - if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) - l1ts->chan_state[chan].lost_frames = 0; + msg->l2h = msgb_put(msg, data_len); + if (data_len) + memcpy(msg->l2h, data, data_len); /* forward primitive */ - l1sap_up(l1t->trx, l1sap); + l1sap_up(l1ts->ts->trx, l1sap); return 0; } -int _sched_compose_tch_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t *tch, uint8_t tch_len) +int _sched_compose_tch_ind(struct l1sched_ts *l1ts, uint32_t fn, + enum trx_chan_type chan, + const uint8_t *data, size_t data_len, + uint16_t ber10k, float rssi, + int16_t ta_offs_256bits, int16_t link_qual_cb, + uint8_t is_sub) { struct msgb *msg; struct osmo_phsap_prim *l1sap; - struct gsm_bts_trx *trx = l1t->trx; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; - struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; + uint8_t chan_nr = trx_chan_desc[chan].chan_nr | l1ts->ts->nr; + struct gsm_lchan *lchan = &l1ts->ts->lchan[l1sap_chan2ss(chan_nr)]; + + /* VAMOS: use Osmocom specific channel number */ + if (l1ts->ts->vamos.is_shadow) + chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK; /* compose primitive */ - msg = l1sap_msgb_alloc(tch_len); + msg = l1sap_msgb_alloc(data_len); l1sap = msgb_l1sap_prim(msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, msg); l1sap->u.tch.chan_nr = chan_nr; l1sap->u.tch.fn = fn; - msg->l2h = msgb_put(msg, tch_len); - if (tch_len) - memcpy(msg->l2h, tch, tch_len); - - if (l1ts->chan_state[chan].lost_frames) - l1ts->chan_state[chan].lost_frames--; - - LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, -1, l1sap->u.data.fn, - "%s Rx -> RTP: %s\n", - gsm_lchan_name(lchan), osmo_hexdump(msgb_l2(msg), msgb_l2len(msg))); + l1sap->u.tch.rssi = (int8_t) (rssi); + l1sap->u.tch.ber10k = ber10k; + l1sap->u.tch.ta_offs_256bits = ta_offs_256bits; + l1sap->u.tch.lqual_cb = link_qual_cb; + l1sap->u.tch.is_sub = is_sub & 1; + + msg->l2h = msgb_put(msg, data_len); + if (data_len) + memcpy(msg->l2h, data, data_len); + + LOGL1S(DL1P, LOGL_DEBUG, l1ts, chan, l1sap->u.tch.fn, "%s Rx -> RTP: %s\n", + gsm_lchan_name(lchan), msgb_hexdump_l2(msg)); /* forward primitive */ - l1sap_up(l1t->trx, l1sap); + l1sap_up(l1ts->ts->trx, l1sap); return 0; } @@ -771,39 +834,37 @@ int _sched_compose_tch_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, * data request (from upper layer) */ -int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) +int trx_sched_ph_data_req(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { - uint8_t tn = l1sap->u.data.chan_nr & 7; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + uint8_t tn = L1SAP_CHAN2TS(l1sap->u.data.chan_nr); + struct l1sched_ts *l1ts = trx->ts[tn].priv; - LOGL1S(DL1P, LOGL_INFO, l1t, tn, -1, l1sap->u.data.fn, + LOGL1S(DL1P, LOGL_DEBUG, l1ts, -1, l1sap->u.data.fn, "PH-DATA.req: chan_nr=0x%02x link_id=0x%02x\n", l1sap->u.data.chan_nr, l1sap->u.data.link_id); - if (!l1sap->oph.msg) - abort(); - /* ignore empty frame */ - if (!msgb_l2len(l1sap->oph.msg)) { + if (!l1sap->oph.msg->l2h || msgb_l2len(l1sap->oph.msg) == 0) { msgb_free(l1sap->oph.msg); return 0; } + /* VAMOS: convert Osmocom specific channel number to a generic one */ + if (trx->ts[tn].vamos.is_shadow) + l1sap->u.data.chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK; + msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg); return 0; } -int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) +int trx_sched_tch_req(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { - uint8_t tn = l1sap->u.tch.chan_nr & 7; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + uint8_t tn = L1SAP_CHAN2TS(l1sap->u.tch.chan_nr); + struct l1sched_ts *l1ts = trx->ts[tn].priv; - LOGL1S(DL1P, LOGL_INFO, l1t, tn, -1, l1sap->u.tch.fn, "TCH.req: chan_nr=0x%02x\n", - l1sap->u.tch.chan_nr); - - if (!l1sap->oph.msg) - abort(); + LOGL1S(DL1P, LOGL_DEBUG, l1ts, -1, l1sap->u.tch.fn, + "TCH.req: chan_nr=0x%02x\n", l1sap->u.tch.chan_nr); /* ignore empty frame */ if (!msgb_l2len(l1sap->oph.msg)) { @@ -811,36 +872,43 @@ int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) return 0; } + /* VAMOS: convert Osmocom specific channel number to a generic one */ + if (trx->ts[tn].vamos.is_shadow) + l1sap->u.tch.chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK; + msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg); return 0; } -/* +/* * ready-to-send indication (to upper layer) */ /* RTS for data frame */ -static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan) +static int rts_data_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br) { uint8_t chan_nr, link_id; struct msgb *msg; struct osmo_phsap_prim *l1sap; /* get data for RTS indication */ - chan_nr = trx_chan_desc[chan].chan_nr | tn; - link_id = trx_chan_desc[chan].link_id; + chan_nr = trx_chan_desc[br->chan].chan_nr | br->tn; + link_id = trx_chan_desc[br->chan].link_id; - if (!chan_nr) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, - "RTS func with non-existing chan_nr %d\n", chan_nr); - return -ENODEV; - } - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, - "PH-RTS.ind: chan_nr=0x%02x link_id=0x%02x\n", chan_nr, link_id); + /* VAMOS: use Osmocom specific channel number */ + if (l1ts->ts->vamos.is_shadow) + chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK; + + /* For handover detection, there are cases where the SACCH should remain inactive until the first RACH + * indicating the TA is received. */ + if (L1SAP_IS_LINK_SACCH(link_id) + && !l1ts->chan_state[br->chan].lchan->want_dl_sacch_active) + return 0; + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "PH-RTS.ind: chan_nr=0x%02x link_id=0x%02x\n", chan_nr, link_id); /* generate prim */ msg = l1sap_msgb_alloc(200); @@ -851,31 +919,30 @@ static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, PRIM_OP_INDICATION, msg); l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.link_id = link_id; - l1sap->u.data.fn = fn; + l1sap->u.data.fn = br->fn; - return l1sap_up(l1t->trx, l1sap); + return l1sap_up(l1ts->ts->trx, l1sap); } -static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, int facch) +static int rts_tch_common(const struct l1sched_ts *l1ts, + const struct trx_dl_burst_req *br, + bool facch) { uint8_t chan_nr, link_id; struct msgb *msg; struct osmo_phsap_prim *l1sap; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); int rc = 0; /* get data for RTS indication */ - chan_nr = trx_chan_desc[chan].chan_nr | tn; - link_id = trx_chan_desc[chan].link_id; + chan_nr = trx_chan_desc[br->chan].chan_nr | br->tn; + link_id = trx_chan_desc[br->chan].link_id; - if (!chan_nr) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, - "RTS func with non-existing chan_nr %d\n", chan_nr); - return -ENODEV; - } - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "TCH RTS.ind: chan_nr=0x%02x\n", chan_nr); + /* VAMOS: use Osmocom specific channel number */ + if (l1ts->ts->vamos.is_shadow) + chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK; + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "TCH RTS.ind: chan_nr=0x%02x\n", chan_nr); /* only send, if FACCH is selected */ if (facch) { @@ -888,13 +955,13 @@ static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, PRIM_OP_INDICATION, msg); l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.link_id = link_id; - l1sap->u.data.fn = fn; + l1sap->u.data.fn = br->fn; - rc = l1sap_up(l1t->trx, l1sap); + rc = l1sap_up(l1ts->ts->trx, l1sap); } - /* dont send, if TCH is in signalling only mode */ - if (l1ts->chan_state[chan].rsl_cmode != RSL_CMOD_SPD_SIGN) { + /* don't send, if TCH is in signalling only mode */ + if (l1ts->chan_state[br->chan].rsl_cmode != RSL_CMOD_SPD_SIGN) { /* generate prim */ msg = l1sap_msgb_alloc(200); if (!msg) @@ -903,128 +970,259 @@ static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, PRIM_OP_INDICATION, msg); l1sap->u.tch.chan_nr = chan_nr; - l1sap->u.tch.fn = fn; + l1sap->u.tch.fn = br->fn; - return l1sap_up(l1t->trx, l1sap); + return l1sap_up(l1ts->ts->trx, l1sap); } return rc; } /* RTS for full rate traffic frame */ -static int rts_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan) +static int rts_tchf_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br) { /* TCH/F may include FACCH on every 4th burst */ - return rts_tch_common(l1t, tn, fn, chan, 1); + return rts_tch_common(l1ts, br, true); } +/* FACCH/H channel mapping for Downlink (see 3GPP TS 45.002, table 1). + * This mapping is valid for both FACCH/H(0) and FACCH/H(1). */ +const uint8_t sched_tchh_dl_facch_map[26] = { + [4] = 1, /* FACCH/H(0): B0(4,6,8,10,13,15) */ + [5] = 1, /* FACCH/H(1): B0(5,7,9,11,14,16) */ + [13] = 1, /* FACCH/H(0): B1(13,15,17,19,21,23) */ + [14] = 1, /* FACCH/H(1): B1(14,16,18,20,22,24) */ + [21] = 1, /* FACCH/H(0): B2(21,23,0,2,4,6) */ + [22] = 1, /* FACCH/H(1): B2(22,24,1,3,5,7) */ +}; /* RTS for half rate traffic frame */ -static int rts_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan) +static int rts_tchh_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br) { - /* the FN 4/5, 13/14, 21/22 defines that FACCH may be included. */ - return rts_tch_common(l1t, tn, fn, chan, ((fn % 26) >> 2) & 1); + return rts_tch_common(l1ts, br, sched_tchh_dl_facch_map[br->fn % 26]); } /* set multiframe scheduler to given pchan */ -int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn, - enum gsm_phys_chan_config pchan) +int trx_sched_set_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config pchan) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - int i; - - i = find_sched_mframe_idx(pchan, tn); + struct l1sched_ts *l1ts = ts->priv; + int i = find_sched_mframe_idx(pchan, ts->nr); if (i < 0) { - LOGP(DL1C, LOGL_NOTICE, "Failed to configure multiframe " - "trx=%d ts=%d\n", l1t->trx->nr, tn); + LOGP(DL1C, LOGL_NOTICE, "%s Failed to configure multiframe (pchan=0x%02x)\n", + gsm_ts_name(ts), pchan); return -ENOTSUP; } l1ts->mf_index = i; l1ts->mf_period = trx_sched_multiframes[i].period; l1ts->mf_frames = trx_sched_multiframes[i].frames; - LOGP(DL1C, LOGL_NOTICE, "Configuring multiframe with %s trx=%d ts=%d\n", - trx_sched_multiframes[i].name, l1t->trx->nr, tn); + if (ts->vamos.peer != NULL) { + l1ts = ts->vamos.peer->priv; + l1ts->mf_index = i; + l1ts->mf_period = trx_sched_multiframes[i].period; + l1ts->mf_frames = trx_sched_multiframes[i].frames; + } + LOGP(DL1C, LOGL_NOTICE, "%s Configured multiframe with '%s'\n", + gsm_ts_name(ts), trx_sched_multiframes[i].name); return 0; } +/* Remove all matching (by chan_nr & link_id) primitives from the given queue */ +static void trx_sched_queue_filter(struct llist_head *q, uint8_t chan_nr, uint8_t link_id) +{ + struct msgb *msg, *_msg; + + llist_for_each_entry_safe(msg, _msg, q, list) { + struct osmo_phsap_prim *l1sap = msgb_l1sap_prim(msg); + switch (l1sap->oph.primitive) { + case PRIM_PH_DATA: + if (l1sap->u.data.chan_nr != chan_nr) + continue; + if (l1sap->u.data.link_id != link_id) + continue; + break; + case PRIM_TCH: + if (l1sap->u.tch.chan_nr != chan_nr) + continue; + if (link_id != 0x00) + continue; + break; + default: + /* Shall not happen */ + OSMO_ASSERT(0); + } + + /* Unlink and free() */ + llist_del(&msg->list); + talloc_free(msg); + } +} + +static void _trx_sched_set_lchan(struct gsm_lchan *lchan, + enum trx_chan_type chan, + bool active) +{ + struct l1sched_ts *l1ts = lchan->ts->priv; + struct l1sched_chan_state *chan_state; + + OSMO_ASSERT(l1ts != NULL); + chan_state = &l1ts->chan_state[chan]; + + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "%s %s\n", + (active) ? "Activating" : "Deactivating", + trx_chan_desc[chan].name); + + if (active) { + /* Clean up everything */ + memset(chan_state, 0, sizeof(*chan_state)); + + /* Bind to generic 'struct gsm_lchan' */ + chan_state->lchan = lchan; + + /* Allocate memory for Rx/Tx burst buffers. Use the maximim size + * of 24 * (2 * 58) bytes, which is sufficient to store up to 24 GMSK + * modulated bursts for CSD or up to 8 8PSK modulated bursts for EGPRS. */ + const size_t buf_size = 24 * GSM_NBITS_NB_GMSK_PAYLOAD; + if (trx_chan_desc[chan].dl_fn != NULL) + chan_state->dl_bursts = talloc_zero_size(l1ts, buf_size); + if (trx_chan_desc[chan].ul_fn != NULL) + chan_state->ul_bursts = talloc_zero_size(l1ts, buf_size); + } else { + chan_state->ho_rach_detect = 0; + + /* Remove pending Tx prims belonging to this lchan */ + trx_sched_queue_filter(&l1ts->dl_prims, + trx_chan_desc[chan].chan_nr, + trx_chan_desc[chan].link_id); + + /* Release memory used by Rx/Tx burst buffers */ + TALLOC_FREE(chan_state->dl_bursts); + TALLOC_FREE(chan_state->ul_bursts); + } + + chan_state->active = active; +} + /* setting all logical channels given attributes to active/inactive */ -int trx_sched_set_lchan(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t link_id, - int active) +int trx_sched_set_lchan(struct gsm_lchan *lchan, uint8_t chan_nr, uint8_t link_id, bool active) { + struct l1sched_ts *l1ts = lchan->ts->priv; uint8_t tn = L1SAP_CHAN2TS(chan_nr); - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); uint8_t ss = l1sap_chan2ss(chan_nr); - int i; - int rc = -EINVAL; + bool found = false; + + if (!l1ts) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "%s lchan with uninitialized scheduler structure\n", + (active) ? "Activating" : "Deactivating"); + return -EINVAL; + } + + /* VAMOS: convert Osmocom specific channel number to a generic one, + * otherwise we won't match anything in trx_chan_desc[]. */ + if (lchan->ts->vamos.is_shadow) + chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK; /* look for all matching chan_nr/link_id */ - for (i = 0; i < _TRX_CHAN_MAX; i++) { - struct l1sched_chan_state *chan_state; - chan_state = &l1ts->chan_state[i]; - /* Skip if pchan type does not match pdch flag. - * FIXME: Is it possible at all? Clarify if so. */ - if ((trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH) - && !(trx_chan_desc[i].flags & TRX_CHAN_FLAG_PDCH)) + for (enum trx_chan_type chan = 0; chan < _TRX_CHAN_MAX; chan++) { + if (trx_chan_desc[chan].chan_nr != (chan_nr & RSL_CHAN_NR_MASK)) continue; - if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8) - && trx_chan_desc[i].link_id == link_id) { - rc = 0; - if (chan_state->active == active) - continue; - LOGP(DL1C, LOGL_NOTICE, "%s %s on trx=%d ts=%d\n", - (active) ? "Activating" : "Deactivating", - trx_chan_desc[i].name, l1t->trx->nr, tn); - if (active) - memset(chan_state, 0, sizeof(*chan_state)); - chan_state->active = active; - /* free burst memory, to cleanly start with burst 0 */ - if (chan_state->dl_bursts) { - talloc_free(chan_state->dl_bursts); - chan_state->dl_bursts = NULL; - } - if (chan_state->ul_bursts) { - talloc_free(chan_state->ul_bursts); - chan_state->ul_bursts = NULL; - } - if (!active) - chan_state->ho_rach_detect = 0; - } + if (trx_chan_desc[chan].link_id != link_id) + continue; + if (l1ts->chan_state[chan].active == active) + continue; + found = true; + _trx_sched_set_lchan(lchan, chan, active); } /* disable handover detection (on deactivation) */ if (!active) - _sched_act_rach_det(l1t, tn, ss, 0); + _sched_act_rach_det(lchan->ts->trx, tn, ss, 0); - return rc; + return found ? 0 : -EINVAL; +} + +int trx_sched_set_ul_access(struct gsm_lchan *lchan, uint8_t chan_nr, bool active) +{ + struct l1sched_ts *l1ts = lchan->ts->priv; + uint8_t tn = L1SAP_CHAN2TS(chan_nr); + uint8_t ss = l1sap_chan2ss(chan_nr); + int i; + + if (!l1ts) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "%s UL access on lchan with uninitialized scheduler structure.\n", + (active) ? "Activating" : "Deactivating"); + return -EINVAL; + } + + /* look for all matching chan_nr */ + for (i = 0; i < _TRX_CHAN_MAX; i++) { + if (trx_chan_desc[i].chan_nr == (chan_nr & RSL_CHAN_NR_MASK)) { + struct l1sched_chan_state *l1cs = &l1ts->chan_state[i]; + + l1cs->ho_rach_detect = active; + } + } + + _sched_act_rach_det(lchan->ts->trx, tn, ss, active); + + return 0; +} + +int trx_sched_set_bcch_ccch(struct gsm_lchan *lchan, bool active) +{ + struct l1sched_ts *l1ts = lchan->ts->priv; + static const enum trx_chan_type chans[] = { + TRXC_FCCH, + TRXC_SCH, + TRXC_BCCH, + TRXC_RACH, + TRXC_CCCH, + }; + + if (!l1ts) + return -EINVAL; + + for (unsigned int i = 0; i < ARRAY_SIZE(chans); i++) { + enum trx_chan_type chan = chans[i]; + + if (l1ts->chan_state[chan].active == active) + continue; + _trx_sched_set_lchan(lchan, chan, active); + } + + return 0; } /* setting all logical channels given attributes to active/inactive */ -int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmode, +int trx_sched_set_mode(struct gsm_bts_trx_ts *ts, uint8_t chan_nr, uint8_t rsl_cmode, uint8_t tch_mode, int codecs, uint8_t codec0, uint8_t codec1, uint8_t codec2, uint8_t codec3, uint8_t initial_id, uint8_t handover) { + struct l1sched_ts *l1ts = ts->priv; uint8_t tn = L1SAP_CHAN2TS(chan_nr); - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); uint8_t ss = l1sap_chan2ss(chan_nr); int i; int rc = -EINVAL; - struct l1sched_chan_state *chan_state; /* no mode for PDCH */ - if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH) + if (ts->pchan == GSM_PCHAN_PDCH) return 0; + /* VAMOS: convert Osmocom specific channel number to a generic one, + * otherwise we won't match anything in trx_chan_desc[]. */ + if (ts->vamos.is_shadow) + chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK; + /* look for all matching chan_nr/link_id */ for (i = 0; i < _TRX_CHAN_MAX; i++) { if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8) && trx_chan_desc[i].link_id == 0x00) { - chan_state = &l1ts->chan_state[i]; - LOGP(DL1C, LOGL_NOTICE, "Set mode %u, %u, handover %u " - "on %s of trx=%d ts=%d\n", rsl_cmode, tch_mode, - handover, trx_chan_desc[i].name, l1t->trx->nr, - tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[i]; + + LOGP(DL1C, LOGL_INFO, + "%s Set mode for %s (rsl_cmode=%u, tch_mode=%u, handover=%u)\n", + gsm_ts_name(ts), trx_chan_desc[i].name, + rsl_cmode, tch_mode, handover); + chan_state->rsl_cmode = rsl_cmode; chan_state->tch_mode = tch_mode; chan_state->ho_rach_detect = handover; @@ -1039,8 +1237,8 @@ int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmo chan_state->dl_ft = initial_id; chan_state->ul_cmr = initial_id; chan_state->dl_cmr = initial_id; - chan_state->ber_sum = 0; - chan_state->ber_num = 0; + chan_state->lqual_cb_sum = 0; + chan_state->lqual_cb_num = 0; } rc = 0; } @@ -1051,53 +1249,54 @@ int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmo * of transceiver link). * disable handover, if state is still set, since we might not know * the actual state of transceiver (due to loss of link) */ - _sched_act_rach_det(l1t, tn, ss, handover); + _sched_act_rach_det(ts->trx, tn, ss, handover); return rc; } /* setting cipher on logical channels */ -int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink, - int algo, uint8_t *key, int key_len) +int trx_sched_set_cipher(struct gsm_lchan *lchan, uint8_t chan_nr, bool downlink) { - uint8_t tn = L1SAP_CHAN2TS(chan_nr); - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - int i; - int rc = -EINVAL; - struct l1sched_chan_state *chan_state; + int algo = lchan->encr.alg_id - 1; + int i, rc = -EINVAL; /* no cipher for PDCH */ - if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH) + if (ts_pchan(lchan->ts) == GSM_PCHAN_PDCH) return 0; + /* VAMOS: convert Osmocom specific channel number to a generic one, + * otherwise we won't match anything in trx_chan_desc[]. */ + if (lchan->ts->vamos.is_shadow) + chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK; + /* no algorithm given means a5/0 */ if (algo <= 0) algo = 0; - else if (key_len != 8) { - LOGP(DL1C, LOGL_ERROR, "Algo A5/%d not supported with given " - "key len=%d\n", algo, key_len); + else if (lchan->encr.key_len != 8 && lchan->encr.key_len != 16) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, + "Algo A5/%d not supported with given key_len=%u\n", + algo, lchan->encr.key_len); return -ENOTSUP; } /* look for all matching chan_nr */ for (i = 0; i < _TRX_CHAN_MAX; i++) { - /* skip if pchan type */ - if (trx_chan_desc[i].flags & TRX_CHAN_FLAG_PDCH) - continue; - if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8)) { - chan_state = &l1ts->chan_state[i]; - LOGP(DL1C, LOGL_NOTICE, "Set a5/%d %s for %s on trx=%d " - "ts=%d\n", algo, - (downlink) ? "downlink" : "uplink", - trx_chan_desc[i].name, l1t->trx->nr, tn); + if (trx_chan_desc[i].chan_nr == (chan_nr & RSL_CHAN_NR_MASK)) { + struct l1sched_ts *l1ts = lchan->ts->priv; + struct l1sched_chan_state *l1cs = &l1ts->chan_state[i]; + + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "Set A5/%d %s for %s\n", + algo, (downlink) ? "downlink" : "uplink", + trx_chan_desc[i].name); + if (downlink) { - chan_state->dl_encr_algo = algo; - memcpy(chan_state->dl_encr_key, key, key_len); - chan_state->dl_encr_key_len = key_len; + l1cs->dl_encr_algo = algo; + memcpy(l1cs->dl_encr_key, lchan->encr.key, lchan->encr.key_len); + l1cs->dl_encr_key_len = lchan->encr.key_len; } else { - chan_state->ul_encr_algo = algo; - memcpy(chan_state->ul_encr_key, key, key_len); - chan_state->ul_encr_key_len = key_len; + l1cs->ul_encr_algo = algo; + memcpy(l1cs->ul_encr_key, lchan->encr.key, lchan->encr.key_len); + l1cs->ul_encr_key_len = lchan->encr.key_len; } rc = 0; } @@ -1107,9 +1306,8 @@ int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink, } /* process ready-to-send */ -int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn) +int _sched_rts(const struct l1sched_ts *l1ts, uint32_t fn) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); const struct trx_sched_frame *frame; uint8_t offset, period, bid; trx_sched_rts_func *func; @@ -1137,87 +1335,99 @@ int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn) return 0; /* check if channel is active */ - if (!TRX_CHAN_IS_ACTIVE(&l1ts->chan_state[chan], chan)) + if (!l1ts->chan_state[chan].active) return -EINVAL; - return func(l1t, tn, fn, frame->dl_chan); + /* There is no burst, just for logging */ + struct trx_dl_burst_req dbr = { + .fn = fn, + .tn = l1ts->ts->nr, + .bid = bid, + .chan = chan, + }; + + return func(l1ts, &dbr); +} + +static void trx_sched_apply_att(const struct gsm_lchan *lchan, + struct trx_dl_burst_req *br) +{ + const struct trx_chan_desc *desc = &trx_chan_desc[br->chan]; + + /* Current BS power reduction value in dB */ + br->att = lchan->bs_power_ctrl.current; + + /* Temporary Overpower for SACCH/FACCH bursts */ + if (!lchan->top_acch_active) + return; + if ((lchan->top_acch_cap.sacch_enable && desc->link_id == LID_SACCH) || + (lchan->top_acch_cap.facch_enable && br->flags & TRX_BR_F_FACCH)) { + if (br->att > lchan->top_acch_cap.overpower_db) + br->att -= lchan->top_acch_cap.overpower_db; + else + br->att = 0; + } } /* process downlink burst */ -const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn, - uint32_t fn, uint16_t *nbits) +void _sched_dl_burst(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - struct l1sched_chan_state *l1cs; + const struct l1sched_chan_state *l1cs; const struct trx_sched_frame *frame; - uint8_t offset, period, bid; + uint8_t offset, period; trx_sched_dl_func *func; - enum trx_chan_type chan; - ubit_t *bits = NULL; if (!l1ts->mf_index) - goto no_data; + return; /* get frame from multiframe */ period = l1ts->mf_period; - offset = fn % period; + offset = br->fn % period; frame = l1ts->mf_frames + offset; - chan = frame->dl_chan; - bid = frame->dl_bid; - func = trx_chan_desc[chan].dl_fn; + br->chan = frame->dl_chan; + br->bid = frame->dl_bid; + func = trx_chan_desc[br->chan].dl_fn; - l1cs = &l1ts->chan_state[chan]; + l1cs = &l1ts->chan_state[br->chan]; /* check if channel is active */ - if (!TRX_CHAN_IS_ACTIVE(l1cs, chan)) { - if (nbits) - *nbits = GSM_BURST_LEN; - goto no_data; - } + if (!l1cs->active) + return; + + /* Training Sequence Code and Set */ + br->tsc_set = l1ts->ts->tsc_set; + br->tsc = l1ts->ts->tsc; /* get burst from function */ - bits = func(l1t, tn, fn, chan, bid, nbits); + if (func(l1ts, br) != 0) + return; + + /* Modulation is indicated by func() */ + br->mod = l1cs->dl_mod_type; + + /* BS Power reduction (in dB) per logical channel */ + if (l1cs->lchan != NULL) + trx_sched_apply_att(l1cs->lchan, br); /* encrypt */ - if (bits && l1cs->dl_encr_algo) { + if (br->burst_len && l1cs->dl_encr_algo) { ubit_t ks[114]; int i; - osmo_a5(l1cs->dl_encr_algo, l1cs->dl_encr_key, fn, ks, NULL); + osmo_a5(l1cs->dl_encr_algo, l1cs->dl_encr_key, br->fn, ks, NULL); for (i = 0; i < 57; i++) { - bits[i + 3] ^= ks[i]; - bits[i + 88] ^= ks[i + 57]; + br->burst[i + 3] ^= ks[i]; + br->burst[i + 88] ^= ks[i + 57]; } } - -no_data: - /* in case of C0, we need a dummy burst to maintain RF power */ - if (bits == NULL && l1t->trx == l1t->trx->bts->c0) { -#if 0 - if (chan != TRXC_IDLE) // hack - LOGP(DL1C, LOGL_DEBUG, "No burst data for %s fn=%u ts=%u " - "burst=%d on C0, so filling with dummy burst\n", - trx_chan_desc[chan].name, fn, tn, bid); -#endif - bits = (ubit_t *) dummy_burst; - } - - return bits; } -#define TDMA_FN_SUM(a, b) \ - ((a + GSM_HYPERFRAME + b) % GSM_HYPERFRAME) - -#define TDMA_FN_SUB(a, b) \ - ((a + GSM_HYPERFRAME - b) % GSM_HYPERFRAME) - -static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, - struct l1sched_chan_state *l1cs, uint8_t tn, uint32_t fn) +static int trx_sched_calc_frame_loss(struct l1sched_ts *l1ts, + struct l1sched_chan_state *l1cs, + const struct trx_ul_burst_ind *bi) { - const struct trx_sched_frame *frame_head; const struct trx_sched_frame *frame; - struct l1sched_ts *l1ts; uint32_t elapsed_fs; uint8_t offset, i; uint32_t fn_i; @@ -1230,13 +1440,8 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, if (l1cs->proc_tdma_fs == 0) return 0; - /* Get current TDMA frame info */ - l1ts = l1sched_trx_get_ts(l1t, tn); - offset = fn % l1ts->mf_period; - frame_head = l1ts->mf_frames + offset; - /* Not applicable for some logical channels */ - switch (frame_head->ul_chan) { + switch (bi->chan) { case TRXC_IDLE: case TRXC_RACH: case TRXC_PDTCH: @@ -1249,9 +1454,9 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, } /* How many frames elapsed since the last one? */ - elapsed_fs = TDMA_FN_SUB(fn, l1cs->last_tdma_fn); + elapsed_fs = GSM_TDMA_FN_SUB(bi->fn, l1cs->last_tdma_fn); if (elapsed_fs > l1ts->mf_period) { /* Too many! */ - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, frame_head->ul_chan, fn, + LOGL1SB(DL1P, LOGL_ERROR, l1ts, bi, "Too many (>%u) contiguous TDMA frames=%u elapsed " "since the last processed fn=%u\n", l1ts->mf_period, elapsed_fs, l1cs->last_tdma_fn); @@ -1263,21 +1468,21 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, * There are several TDMA frames between the last processed * frame and currently received one. Let's walk through this * path and count potentially lost frames, i.e. for which - * we didn't receive the corresponsing UL bursts. + * we didn't receive the corresponding UL bursts. * * Start counting from the last_fn + 1. */ for (i = 1; i < elapsed_fs; i++) { - fn_i = TDMA_FN_SUM(l1cs->last_tdma_fn, i); + fn_i = GSM_TDMA_FN_SUM(l1cs->last_tdma_fn, i); offset = fn_i % l1ts->mf_period; frame = l1ts->mf_frames + offset; - if (frame->ul_chan == frame_head->ul_chan) + if (frame->ul_chan == bi->chan) l1cs->lost_tdma_fs++; } if (l1cs->lost_tdma_fs > 0) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, frame_head->ul_chan, fn, + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi, "At least %u TDMA frames were lost since the last " "processed fn=%u\n", l1cs->lost_tdma_fs, l1cs->last_tdma_fn); @@ -1290,31 +1495,33 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, trx_sched_ul_func *func; /* Prepare dummy burst indication */ - struct trx_ul_burst_ind bi = { + struct trx_ul_burst_ind dbi = { .flags = TRX_BI_F_NOPE_IND, .burst_len = GSM_BURST_LEN, .burst = { 0 }, .rssi = -128, .toa256 = 0, + .chan = bi->chan, /* TDMA FN is set below */ - .tn = tn, + .tn = bi->tn, }; for (i = 1; i < elapsed_fs; i++) { - fn_i = TDMA_FN_SUM(l1cs->last_tdma_fn, i); + fn_i = GSM_TDMA_FN_SUM(l1cs->last_tdma_fn, i); offset = fn_i % l1ts->mf_period; frame = l1ts->mf_frames + offset; func = trx_chan_desc[frame->ul_chan].ul_fn; - if (frame->ul_chan != frame_head->ul_chan) + if (frame->ul_chan != bi->chan) continue; - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, frame->ul_chan, fn, - "Substituting lost TDMA frame=%u by all-zero " - "dummy burst\n", fn_i); + dbi.bid = frame->ul_bid; + dbi.fn = fn_i; - bi.fn = fn_i; - func(l1t, frame->ul_chan, frame->ul_bid, &bi); + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, &dbi, + "Substituting lost burst with NOPE.ind\n"); + + func(l1ts, &dbi); l1cs->lost_tdma_fs--; } @@ -1323,15 +1530,42 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, return 0; } +/* Process a single noise measurement for an inactive timeslot. */ +static void trx_sched_noise_meas(struct l1sched_chan_state *l1cs, + const struct trx_ul_burst_ind *bi) +{ + int *Avg = &l1cs->meas.interf_avg; + + /* EWMA (Exponentially Weighted Moving Average): + * + * Avg[n] = a * Val[n] + (1 - a) * Avg[n - 1] + * + * Implemented using the '+=' operator: + * + * Avg += a * Val - a * Avg + * Avg += a * (Val - Avg) + * + * We use constant 'a' = 0.5, what is equal to: + * + * Avg += (Val - Avg) / 2 + * + * We don't really need precisity here, so no scaling. + */ + + *Avg += (bi->rssi - *Avg) / 2; +} + /* Process an Uplink burst indication */ -int trx_sched_ul_burst(struct l1sched_trx *l1t, struct trx_ul_burst_ind *bi) +int trx_sched_ul_burst(struct l1sched_ts *l1ts, struct trx_ul_burst_ind *bi) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn); struct l1sched_chan_state *l1cs; const struct trx_sched_frame *frame; - uint8_t offset, period, bid; + uint8_t offset, period; trx_sched_ul_func *func; - enum trx_chan_type chan; + + /* VAMOS: redirect to the shadow timeslot */ + if (bi->flags & TRX_BI_F_SHADOW_IND) + l1ts = l1ts->ts->vamos.peer->priv; if (!l1ts->mf_index) return -EINVAL; @@ -1341,26 +1575,37 @@ int trx_sched_ul_burst(struct l1sched_trx *l1t, struct trx_ul_burst_ind *bi) offset = bi->fn % period; frame = l1ts->mf_frames + offset; - chan = frame->ul_chan; - bid = frame->ul_bid; - l1cs = &l1ts->chan_state[chan]; - func = trx_chan_desc[chan].ul_fn; + bi->chan = frame->ul_chan; + bi->bid = frame->ul_bid; + l1cs = &l1ts->chan_state[bi->chan]; + func = trx_chan_desc[bi->chan].ul_fn; /* check if channel is active */ - if (!TRX_CHAN_IS_ACTIVE(l1cs, chan)) - return -EINVAL; + if (!l1cs->active) { + /* handle noise measurements on dedicated and idle channels */ + if (TRX_CHAN_IS_DEDIC(bi->chan) || bi->chan == TRXC_IDLE) + trx_sched_noise_meas(l1cs, bi); + return 0; + } /* omit bursts which have no handler, like IDLE bursts */ if (!func) return -EINVAL; /* calculate how many TDMA frames were potentially lost */ - trx_sched_calc_frame_loss(l1t, l1cs, bi->tn, bi->fn); + trx_sched_calc_frame_loss(l1ts, l1cs, bi); /* update TDMA frame counters */ l1cs->last_tdma_fn = bi->fn; l1cs->proc_tdma_fs++; + /* handle NOPE indications */ + if (bi->flags & TRX_BI_F_NOPE_IND) { + /* NOTE: Uplink burst handler must check bi->burst_len before + * accessing bi->burst to avoid uninitialized memory access. */ + return func(l1ts, bi); + } + /* decrypt */ if (bi->burst_len && l1cs->ul_encr_algo) { ubit_t ks[114]; @@ -1376,13 +1621,7 @@ int trx_sched_ul_burst(struct l1sched_trx *l1t, struct trx_ul_burst_ind *bi) } /* Invoke the logical channel handler */ - func(l1t, chan, bid, bi); + func(l1ts, bi); return 0; } - -struct l1sched_ts *l1sched_trx_get_ts(struct l1sched_trx *l1t, uint8_t tn) -{ - OSMO_ASSERT(tn < ARRAY_SIZE(l1t->ts)); - return &l1t->ts[tn]; -} |