diff options
-rw-r--r-- | include/osmo-bts/Makefile.am | 2 | ||||
-rw-r--r-- | include/osmo-bts/scheduler.h | 175 | ||||
-rw-r--r-- | include/osmo-bts/scheduler_backend.h | 82 | ||||
-rw-r--r-- | src/common/Makefile.am | 4 | ||||
-rw-r--r-- | src/common/scheduler.c (renamed from src/osmo-bts-trx/scheduler.c) | 1910 | ||||
-rw-r--r-- | src/osmo-bts-trx/Makefile.am | 6 | ||||
-rw-r--r-- | src/osmo-bts-trx/l1_if.c | 37 | ||||
-rw-r--r-- | src/osmo-bts-trx/l1_if.h | 121 | ||||
-rw-r--r-- | src/osmo-bts-trx/loops.c | 83 | ||||
-rw-r--r-- | src/osmo-bts-trx/loops.h | 14 | ||||
-rw-r--r-- | src/osmo-bts-trx/main.c | 2 | ||||
-rw-r--r-- | src/osmo-bts-trx/scheduler.h | 52 | ||||
-rw-r--r-- | src/osmo-bts-trx/scheduler_trx.c | 1470 | ||||
-rw-r--r-- | src/osmo-bts-trx/trx_if.c | 13 | ||||
-rw-r--r-- | src/osmo-bts-trx/trx_if.h | 1 | ||||
-rw-r--r-- | src/osmo-bts-trx/trx_vty.c | 2 |
16 files changed, 2061 insertions, 1913 deletions
diff --git a/include/osmo-bts/Makefile.am b/include/osmo-bts/Makefile.am index bf037ae3..af88a1ac 100644 --- a/include/osmo-bts/Makefile.am +++ b/include/osmo-bts/Makefile.am @@ -1,4 +1,4 @@ noinst_HEADERS = abis.h bts.h bts_model.h gsm_data.h logging.h measurement.h \ oml.h paging.h rsl.h signal.h vty.h amr.h pcu_if.h pcuif_proto.h \ handover.h msg_utils.h tx_power.h control_if.h cbch.h l1sap.h \ - power_control.h + power_control.h scheduler.h scheduler_backend.h diff --git a/include/osmo-bts/scheduler.h b/include/osmo-bts/scheduler.h new file mode 100644 index 00000000..13ef0511 --- /dev/null +++ b/include/osmo-bts/scheduler.h @@ -0,0 +1,175 @@ +#ifndef TRX_SCHEDULER_H +#define TRX_SCHEDULER_H + +/* These types define the different channels on a multiframe. + * Each channel has queues and can be activated individually. + */ +enum trx_chan_type { + TRXC_IDLE = 0, + TRXC_FCCH, + TRXC_SCH, + TRXC_BCCH, + TRXC_RACH, + TRXC_CCCH, + TRXC_TCHF, + TRXC_TCHH_0, + TRXC_TCHH_1, + TRXC_SDCCH4_0, + TRXC_SDCCH4_1, + TRXC_SDCCH4_2, + TRXC_SDCCH4_3, + TRXC_SDCCH8_0, + TRXC_SDCCH8_1, + TRXC_SDCCH8_2, + TRXC_SDCCH8_3, + TRXC_SDCCH8_4, + TRXC_SDCCH8_5, + TRXC_SDCCH8_6, + TRXC_SDCCH8_7, + TRXC_SACCHTF, + TRXC_SACCHTH_0, + TRXC_SACCHTH_1, + TRXC_SACCH4_0, + TRXC_SACCH4_1, + TRXC_SACCH4_2, + TRXC_SACCH4_3, + TRXC_SACCH8_0, + TRXC_SACCH8_1, + TRXC_SACCH8_2, + TRXC_SACCH8_3, + TRXC_SACCH8_4, + TRXC_SACCH8_5, + TRXC_SACCH8_6, + TRXC_SACCH8_7, + TRXC_PDTCH, + TRXC_PTCCH, + _TRX_CHAN_MAX +}; + +/* States each channel on a multiframe */ +struct l1sched_chan_state { + /* scheduler */ + uint8_t active; /* Channel is active */ + ubit_t *dl_bursts; /* burst buffer for TX */ + sbit_t *ul_bursts; /* burst buffer for RX */ + uint32_t ul_first_fn; /* fn of first burst */ + uint8_t ul_mask; /* mask of received bursts */ + + /* RSSI / TOA */ + uint8_t rssi_num; /* number of RSSI values */ + float rssi_sum; /* sum of RSSI values */ + uint8_t toa_num; /* number of TOA values */ + float toa_sum; /* sum of TOA values */ + + /* loss detection */ + uint8_t lost; /* (SACCH) loss detection */ + + /* mode */ + uint8_t rsl_cmode, tch_mode; /* mode for TCH channels */ + + /* AMR */ + uint8_t codec[4]; /* 4 possible codecs for amr */ + int codecs; /* number of possible codecs */ + float ber_sum; /* sum of bit error rates */ + int ber_num; /* number of bit error rates */ + uint8_t ul_ft; /* current uplink FT index */ + uint8_t dl_ft; /* current downlink FT index */ + uint8_t ul_cmr; /* current uplink CMR index */ + uint8_t dl_cmr; /* current downlink CMR index */ + uint8_t amr_loop; /* if AMR loop is enabled */ + + /* TCH/H */ + uint8_t dl_ongoing_facch; /* FACCH/H on downlink */ + uint8_t ul_ongoing_facch; /* FACCH/H on uplink */ + + /* encryption */ + int ul_encr_algo; /* A5/x encry algo downlink */ + int dl_encr_algo; /* A5/x encry algo uplink */ + int ul_encr_key_len; + int dl_encr_key_len; + uint8_t ul_encr_key[MAX_A5_KEY_LEN]; + uint8_t dl_encr_key[MAX_A5_KEY_LEN]; + + /* measurements */ + struct { + uint8_t clock; /* cyclic clock counter */ + int8_t rssi[32]; /* last RSSI values */ + int rssi_count; /* received RSSI values */ + int rssi_valid_count; /* number of stored value */ + int rssi_got_burst; /* any burst received so far */ + float toa_sum; /* sum of TOA values */ + int toa_num; /* number of TOA value */ + } meas; + + /* handover */ + uint8_t ho_rach_detect; /* if rach detection is on */ +}; + +struct l1sched_ts { + uint8_t mf_index; /* selected multiframe index */ + uint32_t mf_last_fn; /* last received frame number */ + uint8_t mf_period; /* period of multiframe */ + const struct trx_sched_frame *mf_frames; /* pointer to frame layout */ + + struct llist_head dl_prims; /* Queue primitves for TX */ + + /* Channel states for all logical channels */ + struct l1sched_chan_state chan_state[_TRX_CHAN_MAX]; +}; + +struct l1sched_trx { + struct gsm_bts_trx *trx; + struct l1sched_ts ts[TRX_NR_TS]; +}; + +struct l1sched_ts *l1sched_trx_get_ts(struct l1sched_trx *l1t, uint8_t tn); + +/*! \brief how many frame numbers in advance we should send bursts to PHY */ +extern uint32_t trx_clock_advance; +/*! \brief advance RTS.ind to L2 by that many clocks */ +extern uint32_t trx_rts_advance; +/*! \brief last frame number as received from PHY */ +extern uint32_t transceiver_last_fn; + + +/*! \brief Initialize the scheudler data structures */ +int trx_sched_init(struct l1sched_trx *l1t); + +/*! \brief De-initialize the scheudler data structures */ +void trx_sched_exit(struct l1sched_trx *l1t); + +/*! \brief Handle a PH-DATA.req from L2 down to L1 */ +int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap); + +/*! \brief Handle a PH-TCH.req from L2 down to L1 */ +int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap); + +/*! \brief PHY informs us of new (current) GSM freme nunmber */ +int trx_sched_clock(struct gsm_bts *bts, uint32_t fn); + +/*! \brief handle an UL burst received by PHY */ +int trx_sched_ul_burst(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + sbit_t *bits, int8_t rssi, float toa); + +/*! \brief set multiframe scheduler to given physical channel config */ +int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn, + enum gsm_phys_chan_config pchan); + +/*! \brief set all matching logical channels active/inactive */ +int trx_sched_set_lchan(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t link_id, + int active); + +/*! \brief set mode of all matching logical channels to given mode(s) */ +int trx_sched_set_mode(struct l1sched_trx *l1t, 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_codec, + uint8_t handover); + +/*! \brief set ciphering on given 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); + +/* \brief close all logical channels and reset timeslots */ +void trx_sched_reset(struct l1sched_trx *l1t); + +#endif /* TRX_SCHEDULER_H */ diff --git a/include/osmo-bts/scheduler_backend.h b/include/osmo-bts/scheduler_backend.h new file mode 100644 index 00000000..9f663cdd --- /dev/null +++ b/include/osmo-bts/scheduler_backend.h @@ -0,0 +1,82 @@ +#pragma once + +typedef int trx_sched_rts_func(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, enum trx_chan_type chan); + +typedef ubit_t *trx_sched_dl_func(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, enum trx_chan_type chan, + uint8_t bid); + +typedef int trx_sched_ul_func(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, enum trx_chan_type chan, + uint8_t bid, sbit_t *bits, int8_t rssi, + float toa); + +struct trx_chan_desc { + /*! \brief Is this on a PDCH (PS) ? */ + int pdch; + /*! \brief TRX Channel Type */ + enum trx_chan_type chan; + /*! \brief Channel Number (like in RSL) */ + uint8_t chan_nr; + /*! \brief Link ID (like in RSL) */ + uint8_t link_id; + /*! \brief Human-readable name */ + const char *name; + /*! \brief function to call when we want to generate RTS.req to L2 */ + trx_sched_rts_func *rts_fn; + /*! \brief function to call when DATA.req received from L2 */ + trx_sched_dl_func *dl_fn; + /*! \brief function to call when burst received from PHY */ + trx_sched_ul_func *ul_fn; + /*! \breif is this channel automatically active at start? */ + int auto_active; +}; +extern const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX]; + +extern const ubit_t _sched_tsc[8][26]; +const ubit_t _sched_fcch_burst[148]; +const ubit_t _sched_sch_train[64]; + +struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn, + enum trx_chan_type chan); + +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_tch_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *tch, uint8_t tch_len); + +ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, + float toa); +int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, + float toa); +int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, + float toa); +int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, + float toa); +int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, + float toa); + +const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn); +int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn); +void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate); diff --git a/src/common/Makefile.am b/src/common/Makefile.am index e577eba6..fea205c1 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -2,10 +2,12 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOTRAU_CFLAGS) LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOTRAU_LIBS) -noinst_LIBRARIES = libbts.a +noinst_LIBRARIES = libbts.a libl1sched.a libbts_a_SOURCES = gsm_data_shared.c sysinfo.c logging.c abis.c oml.c bts.c \ rsl.c vty.c paging.c measurement.c amr.c lchan.c \ load_indication.c pcu_sock.c handover.c msg_utils.c \ load_indication.c pcu_sock.c handover.c msg_utils.c \ tx_power.c bts_ctrl_commands.c bts_ctrl_lookup.c \ l1sap.c cbch.c power_control.c main.c + +libl1sched_a_SOURCES = scheduler.c diff --git a/src/osmo-bts-trx/scheduler.c b/src/common/scheduler.c index 1710b4e5..64d89ac4 100644 --- a/src/osmo-bts-trx/scheduler.c +++ b/src/common/scheduler.c @@ -2,6 +2,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> * * All Rights Reserved * @@ -30,87 +31,21 @@ #include <osmocom/core/bits.h> #include <osmocom/gsm/a5.h> -#include <osmocom/netif/rtp.h> - #include <osmo-bts/gsm_data.h> #include <osmo-bts/logging.h> #include <osmo-bts/rsl.h> #include <osmo-bts/l1sap.h> -#include <osmo-bts/amr.h> - -#include "l1_if.h" -#include "scheduler.h" -#include "gsm0503_coding.h" -#include "trx_if.h" -#include "loops.h" -#include "amr.h" -#include "loops.h" - -/* Enable this to multiply TOA of RACH by 10. - * This is usefull to check tenth of timing advances with RSSI test tool. - * Note that regular phones will not work when using this test! */ -//#define TA_TEST - -void *tall_bts_ctx; - -static struct gsm_bts *bts; +#include <osmo-bts/scheduler.h> +#include <osmo-bts/scheduler_backend.h> -/* clock states */ -static uint32_t transceiver_lost; -uint32_t transceiver_last_fn; -static struct timeval transceiver_clock_tv; -static struct osmo_timer_list transceiver_clock_timer; - -/* clock advance for the transceiver */ -uint32_t trx_clock_advance = 20; - -/* advance RTS to give some time for data processing. (especially PCU) */ -uint32_t trx_rts_advance = 5; /* about 20ms */ - -typedef int trx_sched_rts_func(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan); -typedef ubit_t *trx_sched_dl_func(struct trx_l1h *l1h, uint8_t tn, - uint32_t fn, enum trx_chan_type chan, uint8_t bid); -typedef int trx_sched_ul_func(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, - float toa); +extern void *tall_bts_ctx; -static int rts_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, +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 trx_l1h *l1h, uint8_t tn, uint32_t fn, +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 trx_l1h *l1h, uint8_t tn, uint32_t fn, +static int rts_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, enum trx_chan_type chan); -static ubit_t *tx_idle_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid); -static ubit_t *tx_fcch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid); -static ubit_t *tx_sch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid); -static ubit_t *tx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid); -static ubit_t *tx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid); -static ubit_t *tx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid); -static ubit_t *tx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid); -static int rx_rach_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, - float toa); -static int rx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, - float toa); -static int rx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, - float toa); -static int rx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, - float toa); -static int rx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, - float toa); - /*! \brief Dummy Burst (TS 05.02 Chapter 5.2.6) */ static const ubit_t dummy_burst[148] = { 0,0,0, @@ -123,7 +58,7 @@ static const ubit_t dummy_burst[148] = { }; /*! \brief FCCH Burst (TS 05.02 Chapter 5.2.4) */ -static const ubit_t fcch_burst[148] = { +const ubit_t _sched_fcch_burst[148] = { 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, @@ -132,7 +67,7 @@ static const ubit_t fcch_burst[148] = { }; /*! \brief Training Sequences (TS 05.02 Chapter 5.2.3) */ -static const ubit_t tsc[8][26] = { +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, }, @@ -144,7 +79,7 @@ static const ubit_t tsc[8][26] = { }; /*! \brief SCH trainign sequence (TS 05.02 Chapter 5.2.5) */ -static const ubit_t sch_train[64] = { +const ubit_t _sched_sch_train[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, }; @@ -153,27 +88,7 @@ static const ubit_t sch_train[64] = { * subchannel description structure */ -struct trx_chan_desc { - /*! \brief Is this on a PDCH (PS) ? */ - int pdch; - /*! \brief TRX Channel Type */ - enum trx_chan_type chan; - /*! \brief Channel Number (like in RSL) */ - uint8_t chan_nr; - /*! \brief Link ID (like in RSL) */ - uint8_t link_id; - /*! \brief Human-readable name */ - const char *name; - /*! \brief function to call when we want to generate RTS.req to L2 */ - trx_sched_rts_func *rts_fn; - /*! \brief function to call when DATA.req received from L2 */ - trx_sched_dl_func *dl_fn; - /*! \brief function to call when burst received from PHY */ - trx_sched_ul_func *ul_fn; - /*! \breif is this channel automatically active at start? */ - int auto_active; -}; -static const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { +const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { { 0, TRXC_IDLE, 0, 0, "IDLE", NULL, tx_idle_fn, NULL, 1 }, { 0, TRXC_FCCH, 0, 0, "FCCH", NULL, tx_fcch_fn, NULL, 1 }, { 0, TRXC_SCH, 0, 0, "SCH", NULL, tx_sch_fn, NULL, 1 }, @@ -219,23 +134,22 @@ static const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { * init / exit */ -int trx_sched_init(struct trx_l1h *l1h) +int trx_sched_init(struct l1sched_trx *l1t) { uint8_t tn; int i; - struct trx_chan_state *chan_state; - LOGP(DL1C, LOGL_NOTICE, "Init scheduler for trx=%u\n", l1h->trx->nr); + LOGP(DL1C, LOGL_NOTICE, "Init scheduler for trx=%u\n", l1t->trx->nr); - /* hack to get bts */ - bts = l1h->trx->bts; + for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - for (tn = 0; tn < TRX_NR_TS; tn++) { - l1h->mf_index[tn] = 0; - l1h->mf_last_fn[tn] = 0; - INIT_LLIST_HEAD(&l1h->dl_prims[tn]); - for (i = 0; i < _TRX_CHAN_MAX; i++) { - chan_state = &l1h->chan_states[tn][i]; + l1ts->mf_index = 0; + l1ts->mf_last_fn = 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; } } @@ -243,18 +157,20 @@ int trx_sched_init(struct trx_l1h *l1h) return 0; } -void trx_sched_exit(struct trx_l1h *l1h) +void trx_sched_exit(struct l1sched_trx *l1t) { + struct gsm_bts_trx_ts *ts; uint8_t tn; int i; - struct trx_chan_state *chan_state; - LOGP(DL1C, LOGL_NOTICE, "Exit scheduler for trx=%u\n", l1h->trx->nr); + LOGP(DL1C, LOGL_NOTICE, "Exit scheduler for trx=%u\n", l1t->trx->nr); - for (tn = 0; tn < TRX_NR_TS; tn++) { - msgb_queue_flush(&l1h->dl_prims[tn]); + 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++) { - chan_state = &l1h->chan_states[tn][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; @@ -265,259 +181,30 @@ void trx_sched_exit(struct trx_l1h *l1h) } } /* clear lchan channel states */ - for (i = 0; i < TRX_NR_TS; i++) - l1h->trx->ts[tn].lchan[i].state = LCHAN_S_NONE; + ts = &l1t->trx->ts[tn]; + for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) + lchan_set_state(&ts->lchan[i], LCHAN_S_NONE); } } /* close all logical channels and reset timeslots */ -void trx_sched_reset(struct trx_l1h *l1h) -{ - trx_sched_exit(l1h); - trx_sched_init(l1h); -} - - -/* - * data request (from upper layer) - */ - -int trx_sched_ph_data_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap) -{ - uint8_t tn = l1sap->u.data.chan_nr & 7; - - LOGP(DL1C, LOGL_INFO, "PH-DATA.req: chan_nr=0x%02x link_id=0x%02x " - "fn=%u ts=%u trx=%u\n", l1sap->u.data.chan_nr, - l1sap->u.data.link_id, l1sap->u.data.fn, tn, l1h->trx->nr); - - if (!l1sap->oph.msg) - abort(); - - /* ignore empty frame */ - if (!msgb_l2len(l1sap->oph.msg)) { - msgb_free(l1sap->oph.msg); - return 0; - } - - msgb_enqueue(&l1h->dl_prims[tn], l1sap->oph.msg); - - return 0; -} - -int trx_sched_tch_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap) +void trx_sched_reset(struct l1sched_trx *l1t) { - uint8_t tn = l1sap->u.tch.chan_nr & 7; - - LOGP(DL1C, LOGL_INFO, "TCH.req: chan_nr=0x%02x " - "fn=%u ts=%u trx=%u\n", l1sap->u.tch.chan_nr, - l1sap->u.tch.fn, tn, l1h->trx->nr); - - if (!l1sap->oph.msg) - abort(); - - /* ignore empty frame */ - if (!msgb_l2len(l1sap->oph.msg)) { - msgb_free(l1sap->oph.msg); - return 0; - } - - msgb_enqueue(&l1h->dl_prims[tn], l1sap->oph.msg); - - return 0; + trx_sched_exit(l1t); + trx_sched_init(l1t); } - -/* - * ready-to-send indication (to upper layer) - */ - -/* RTS for data frame */ -static int rts_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan) -{ - 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; - - if (!chan_nr) { - LOGP(DL1C, LOGL_FATAL, "RTS func for %s with non-existing " - "chan_nr %d\n", trx_chan_desc[chan].name, chan_nr); - return -ENODEV; - } - - LOGP(DL1C, LOGL_INFO, "PH-RTS.ind: chan=%s chan_nr=0x%02x " - "link_id=0x%02x fn=%u ts=%u trx=%u\n", trx_chan_desc[chan].name, - chan_nr, link_id, fn, tn, l1h->trx->nr); - - /* send clock information to loops process */ - if (L1SAP_IS_LINK_SACCH(link_id)) - trx_loop_sacch_clock(l1h, chan_nr, &l1h->chan_states[tn][chan]); - - /* generate prim */ - msg = l1sap_msgb_alloc(200); - if (!msg) - return -ENOMEM; - l1sap = msgb_l1sap_prim(msg); - osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, - PRIM_OP_INDICATION, msg); - l1sap->u.data.chan_nr = chan_nr; - l1sap->u.data.link_id = link_id; - l1sap->u.data.fn = fn; - - return l1sap_up(l1h->trx, l1sap); -} - -static int rts_tch_common(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, int facch) -{ - uint8_t chan_nr, link_id; - struct msgb *msg; - struct osmo_phsap_prim *l1sap; - 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; - - if (!chan_nr) { - LOGP(DL1C, LOGL_FATAL, "RTS func for %s with non-existing " - "chan_nr %d\n", trx_chan_desc[chan].name, chan_nr); - return -ENODEV; - } - - LOGP(DL1C, LOGL_INFO, "TCH RTS.ind: chan=%s chan_nr=0x%02x " - "fn=%u ts=%u trx=%u\n", trx_chan_desc[chan].name, - chan_nr, fn, tn, l1h->trx->nr); - - /* only send, if FACCH is selected */ - if (facch) { - /* generate prim */ - msg = l1sap_msgb_alloc(200); - if (!msg) - return -ENOMEM; - l1sap = msgb_l1sap_prim(msg); - osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, - PRIM_OP_INDICATION, msg); - l1sap->u.data.chan_nr = chan_nr; - l1sap->u.data.link_id = link_id; - l1sap->u.data.fn = fn; - - rc = l1sap_up(l1h->trx, l1sap); - } - - /* dont send, if TCH is in signalling only mode */ - if (l1h->chan_states[tn][chan].rsl_cmode != RSL_CMOD_SPD_SIGN) { - /* generate prim */ - msg = l1sap_msgb_alloc(200); - if (!msg) - return -ENOMEM; - l1sap = msgb_l1sap_prim(msg); - 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; - - return l1sap_up(l1h->trx, l1sap); - } - - return rc; -} - -/* RTS for full rate traffic frame */ -static int rts_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan) -{ - /* TCH/F may include FACCH on every 4th burst */ - return rts_tch_common(l1h, tn, fn, chan, 1); -} - - -/* RTS for half rate traffic frame */ -static int rts_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan) -{ - /* the FN 4/5, 13/14, 21/22 defines that FACCH may be included. */ - return rts_tch_common(l1h, tn, fn, chan, ((fn % 26) >> 2) & 1); -} - - -/* - * TX on downlink - */ - -/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */ -static ubit_t *tx_idle_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid) -{ - LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", - trx_chan_desc[chan].name, fn, tn, l1h->trx->nr); - - return NULL; -} - -static ubit_t *tx_fcch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid) -{ - LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", - trx_chan_desc[chan].name, fn, tn, l1h->trx->nr); - - return (ubit_t *) fcch_burst; -} - -static ubit_t *tx_sch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid) -{ - static ubit_t bits[148], burst[78]; - uint8_t sb_info[4]; - struct gsm_time t; - uint8_t t3p, bsic; - - LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", - trx_chan_desc[chan].name, fn, tn, l1h->trx->nr); - - /* create SB info from GSM time and BSIC */ - gsm_fn2gsmtime(&t, fn); - t3p = t.t3 / 10; - bsic = l1h->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 */ - sch_encode(burst, sb_info); - - /* compose burst */ - memset(bits, 0, 3); - memcpy(bits + 3, burst, 39); - memcpy(bits + 42, sch_train, 64); - memcpy(bits + 106, burst + 39, 39); - memset(bits + 145, 0, 3); - - return bits; -} - -static struct msgb *dequeue_prim(struct trx_l1h *l1h, int8_t tn,uint32_t fn, - enum trx_chan_type chan) +struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn, + enum trx_chan_type chan) { struct msgb *msg, *msg2; struct osmo_phsap_prim *l1sap; uint32_t prim_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, &l1h->dl_prims[tn], list) { + 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: @@ -548,7 +235,7 @@ free_msg: "is out of range, or channel already disabled. " "If this happens in conjunction with PCU, " "increase 'rts-advance' by 5. (current fn=%u)\n", - l1h->trx->nr, tn, l1sap->u.data.fn, fn); + l1t->trx->nr, tn, l1sap->u.data.fn, fn); /* unlink and free message */ llist_del(&msg->list); msgb_free(msg); @@ -578,12 +265,13 @@ found_msg: return msg; } -static int compose_ph_data_ind(struct trx_l1h *l1h, 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_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *l2, uint8_t l2_len, float rssi) { 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); /* compose primitive */ msg = l1sap_msgb_alloc(l2_len); @@ -599,19 +287,20 @@ static int compose_ph_data_ind(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, memcpy(msg->l2h, l2, l2_len); if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) - l1h->chan_states[tn][chan].lost = 0; + l1ts->chan_state[chan].lost = 0; /* forward primitive */ - l1sap_up(l1h->trx, l1sap); + l1sap_up(l1t->trx, l1sap); return 0; } -static int compose_tch_ind(struct trx_l1h *l1h, 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_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *tch, uint8_t tch_len) { struct msgb *msg; struct osmo_phsap_prim *l1sap; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); /* compose primitive */ msg = l1sap_msgb_alloc(tch_len); @@ -624,1106 +313,182 @@ static int compose_tch_ind(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, if (tch_len) memcpy(msg->l2h, tch, tch_len); - if (l1h->chan_states[tn][chan].lost) - l1h->chan_states[tn][chan].lost--; + if (l1ts->chan_state[chan].lost) + l1ts->chan_state[chan].lost--; /* forward primitive */ - l1sap_up(l1h->trx, l1sap); + l1sap_up(l1t->trx, l1sap); return 0; } -static ubit_t *tx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid) -{ - struct msgb *msg = NULL; /* make GCC happy */ - ubit_t *burst, **bursts_p = &l1h->chan_states[tn][chan].dl_bursts; - static ubit_t bits[148]; - - /* 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 = dequeue_prim(l1h, tn, fn, chan); - if (msg) - goto got_msg; - - LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " - "trx=%u ts=%u at fn=%u to transmit.\n", - trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); - -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) { - LOGP(DL1C, LOGL_FATAL, "Prim not 23 bytes, please FIX! " - "(len=%d)\n", msgb_l2len(msg)); - /* free message */ - msgb_free(msg); - goto no_msg; - } - - /* handle loss detection of sacch */ - if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) { - /* count and send BFI */ - if (++(l1h->chan_states[tn][chan].lost) > 1) { - /* TODO: Should we pass old TOA here? Otherwise we risk - * unnecessary decreasing TA */ - - /* Send uplnk measurement information to L2 */ - l1if_process_meas_res(l1h->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, - 456, 456, -110, 0); - - compose_ph_data_ind(l1h, tn, 0, chan, NULL, 0, -110); - } - } - - /* alloc burst memory, if not already */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, 464); - if (!*bursts_p) - return NULL; - } - - /* encode bursts */ - 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, tsc[l1h->config.tsc], 26); - memcpy(bits + 87, burst + 58, 58); - memset(bits + 145, 0, 3); - - LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", - trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); - - return bits; -} - -static ubit_t *tx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid) -{ - struct msgb *msg = NULL; /* make GCC happy */ - ubit_t *burst, **bursts_p = &l1h->chan_states[tn][chan].dl_bursts; - static ubit_t bits[148]; - int rc; - - /* 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 = dequeue_prim(l1h, tn, fn, chan); - if (msg) - goto got_msg; - - LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " - "trx=%u ts=%u at fn=%u to transmit.\n", - trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); -no_msg: - /* free burst memory */ - if (*bursts_p) { - talloc_free(*bursts_p); - *bursts_p = NULL; - } - return NULL; -got_msg: - /* alloc burst memory, if not already */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, 464); - if (!*bursts_p) - return NULL; - } - - /* encode bursts */ - rc = pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h); - - /* check validity of message */ - if (rc) { - LOGP(DL1C, LOGL_FATAL, "Prim invalid length, please FIX! " - "(len=%d)\n", rc); - /* free message */ - msgb_free(msg); - goto no_msg; - } - - /* 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, tsc[l1h->config.tsc], 26); - memcpy(bits + 87, burst + 58, 58); - memset(bits + 145, 0, 3); - - LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", - trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); - - return bits; -} - -static void tx_tch_common(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, struct msgb **_msg_tch, - struct msgb **_msg_facch, int codec_mode_request) -{ - struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL; - struct trx_chan_state *chan_state = &l1h->chan_states[tn][chan]; - uint8_t rsl_cmode = chan_state->rsl_cmode; - uint8_t tch_mode = chan_state->tch_mode; - struct osmo_phsap_prim *l1sap; - - /* handle loss detection of received TCH frames */ - if (rsl_cmode == RSL_CMOD_SPD_SPEECH - && ++(l1h->chan_states[tn][chan].lost) > 5) { - uint8_t tch_data[GSM_FR_BYTES]; - int len; - - LOGP(DL1C, LOGL_NOTICE, "Missing TCH bursts detected, sending " - "BFI for %s\n", trx_chan_desc[chan].name); - - /* 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 = amr_compose_payload(tch_data, - chan_state->codec[chan_state->dl_cmr], - chan_state->codec[chan_state->dl_ft], 1); - if (len < 2) - break; - memset(tch_data + 2, 0, len - 2); - compose_tch_ind(l1h, tn, 0, chan, tch_data, len); - break; - default: -inval_mode1: - LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, please " - "fix!\n"); - len = 0; - } - if (len) - compose_tch_ind(l1h, tn, 0, chan, tch_data, len); - } - - /* get frame and unlink from queue */ - msg1 = dequeue_prim(l1h, tn, fn, chan); - msg2 = dequeue_prim(l1h, 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) { - LOGP(DL1C, LOGL_FATAL, "TCH twice, " - "please FIX! "); - msgb_free(msg2); - } else - msg_facch = msg2; - } - } else { - msg_facch = msg1; - if (msg2) { - l1sap = msgb_l1sap_prim(msg2); - if (l1sap->oph.primitive != PRIM_TCH) { - LOGP(DL1C, LOGL_FATAL, "FACCH twice, " - "please FIX! "); - 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) { - LOGP(DL1C, LOGL_FATAL, "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 bfi, cmr_codec, ft_codec; - int cmr, ft, i; - - if (rsl_cmode != RSL_CMOD_SPD_SPEECH) { - LOGP(DL1C, LOGL_NOTICE, "%s Dropping speech frame, " - "because we are not in speech mode trx=%u " - "ts=%u at fn=%u.\n", trx_chan_desc[chan].name, - l1h->trx->nr, tn, fn); - goto free_bad_msg; - } - - switch (tch_mode) { - case GSM48_CMODE_SPEECH_V1: /* FR / HR */ - if (chan != TRXC_TCHF) { /* HR */ - len = 15; - if (msgb_l2len(msg_tch) >= 1 - && (msg_tch->l2h[0] & 0xf0) != 0x00) { - LOGP(DL1C, LOGL_NOTICE, "%s " - "Transmitting 'bad " - "HR frame' trx=%u ts=%u at " - "fn=%u.\n", - trx_chan_desc[chan].name, - l1h->trx->nr, tn, fn); - goto free_bad_msg; - } - break; - } - len = GSM_FR_BYTES; - if (msgb_l2len(msg_tch) >= 1 - && (msg_tch->l2h[0] >> 4) != 0xd) { - LOGP(DL1C, LOGL_NOTICE, "%s Transmitting 'bad " - "FR frame' trx=%u ts=%u at fn=%u.\n", - trx_chan_desc[chan].name, - l1h->trx->nr, tn, fn); - goto free_bad_msg; - } - break; - case GSM48_CMODE_SPEECH_EFR: /* EFR */ - if (chan != TRXC_TCHF) - goto inval_mode2; - len = GSM_EFR_BYTES; - if (msgb_l2len(msg_tch) >= 1 - && (msg_tch->l2h[0] >> 4) != 0xc) { - LOGP(DL1C, LOGL_NOTICE, "%s Transmitting 'bad " - "EFR frame' trx=%u ts=%u at fn=%u.\n", - trx_chan_desc[chan].name, - l1h->trx->nr, tn, fn); - goto free_bad_msg; - } - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - len = amr_decompose_payload(msg_tch->l2h, - msgb_l2len(msg_tch), &cmr_codec, &ft_codec, - &bfi); - 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) { - LOGP(DL1C, LOGL_ERROR, "%s Codec (FT = %d) " - " of RTP frame not in list. " - "trx=%u ts=%u\n", - trx_chan_desc[chan].name, ft_codec, - l1h->trx->nr, tn); - goto free_bad_msg; - } - if (codec_mode_request && chan_state->dl_ft != ft) { - LOGP(DL1C, LOGL_NOTICE, "%s Codec (FT = %d) " - " of RTP cannot be changed now, but in " - "next frame. trx=%u ts=%u\n", - trx_chan_desc[chan].name, ft_codec, - l1h->trx->nr, tn); - goto free_bad_msg; - } - chan_state->dl_ft = ft; - if (bfi) { - LOGP(DL1C, LOGL_NOTICE, "%s Transmitting 'bad " - "AMR frame' trx=%u ts=%u at fn=%u.\n", - trx_chan_desc[chan].name, - l1h->trx->nr, tn, fn); - goto free_bad_msg; - } - break; - default: -inval_mode2: - LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, please " - "fix!\n"); - goto free_bad_msg; - } - if (len < 0) { - LOGP(DL1C, LOGL_ERROR, "Cannot send invalid AMR " - "payload\n"); - goto free_bad_msg; - } - if (msgb_l2len(msg_tch) != len) { - LOGP(DL1C, LOGL_ERROR, "Cannot send payload with " - "invalid length! (expecing %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; -} +/* + * data request (from upper layer) + */ -static ubit_t *tx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid) +int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) { - struct msgb *msg_tch = NULL, *msg_facch = NULL; - struct trx_chan_state *chan_state = &l1h->chan_states[tn][chan]; - uint8_t tch_mode = chan_state->tch_mode; - ubit_t *burst, **bursts_p = &chan_state->dl_bursts; - static ubit_t bits[148]; - - /* send burst, if we already got a frame */ - if (bid > 0) { - if (!*bursts_p) - return NULL; - goto send_burst; - } + uint8_t tn = l1sap->u.data.chan_nr & 7; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - tx_tch_common(l1h, tn, fn, chan, bid, &msg_tch, &msg_facch, - (((fn + 4) % 26) >> 2) & 1); + LOGP(DL1C, LOGL_INFO, "PH-DATA.req: chan_nr=0x%02x link_id=0x%02x " + "fn=%u ts=%u trx=%u\n", l1sap->u.data.chan_nr, + l1sap->u.data.link_id, l1sap->u.data.fn, tn, l1t->trx->nr); - /* alloc 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); - } + if (!l1sap->oph.msg) + abort(); - /* no message at all */ - if (!msg_tch && !msg_facch) { - LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " - "trx=%u ts=%u at fn=%u to transmit.\n", - trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); - goto send_burst; + /* ignore empty frame */ + if (!msgb_l2len(l1sap->oph.msg)) { + msgb_free(l1sap->oph.msg); + return 0; } - /* encode bursts (priorize FACCH) */ - if (msg_facch) - 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. - */ - tch_afs_encode(*bursts_p, msg_tch->l2h + 2, - msgb_l2len(msg_tch) - 2, (((fn + 4) % 26) >> 2) & 1, - chan_state->codec, chan_state->codecs, - chan_state->dl_ft, - chan_state->dl_cmr); - else - 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, tsc[l1h->config.tsc], 26); - memcpy(bits + 87, burst + 58, 58); - memset(bits + 145, 0, 3); - - LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", - trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); + msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg); - return bits; + return 0; } -static ubit_t *tx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid) +int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) { - struct msgb *msg_tch = NULL, *msg_facch = NULL; - struct trx_chan_state *chan_state = &l1h->chan_states[tn][chan]; - uint8_t tch_mode = chan_state->tch_mode; - ubit_t *burst, **bursts_p = &chan_state->dl_bursts; - static ubit_t bits[148]; - - /* send burst, if we already got a frame */ - if (bid > 0) { - if (!*bursts_p) - return NULL; - goto send_burst; - } + uint8_t tn = l1sap->u.tch.chan_nr & 7; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - /* get TCH and/or FACCH */ - tx_tch_common(l1h, tn, fn, chan, bid, &msg_tch, &msg_facch, - (((fn + 4) % 26) >> 2) & 1); - - /* check for FACCH alignment */ - if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) { - LOGP(DL1C, LOGL_ERROR, "%s Cannot transmit FACCH starting on " - "even frames, please fix RTS!\n", - trx_chan_desc[chan].name); - msgb_free(msg_facch); - msg_facch = NULL; - } + LOGP(DL1C, LOGL_INFO, "TCH.req: chan_nr=0x%02x " + "fn=%u ts=%u trx=%u\n", l1sap->u.tch.chan_nr, + l1sap->u.tch.fn, tn, l1t->trx->nr); - /* alloc 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); - } - } + if (!l1sap->oph.msg) + abort(); - /* no message at all */ - if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) { - LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " - "trx=%u ts=%u at fn=%u to transmit.\n", - trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); - goto send_burst; + /* ignore empty frame */ + if (!msgb_l2len(l1sap->oph.msg)) { + msgb_free(l1sap->oph.msg); + return 0; } - /* encode bursts (priorize FACCH) */ - if (msg_facch) { - 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. */ - tch_ahs_encode(*bursts_p, msg_tch->l2h + 2, - msgb_l2len(msg_tch) - 2, (((fn + 4) % 26) >> 2) & 1, - chan_state->codec, chan_state->codecs, - chan_state->dl_ft, - chan_state->dl_cmr); - else - 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, tsc[l1h->config.tsc], 26); - memcpy(bits + 87, burst + 58, 58); - memset(bits + 145, 0, 3); - - LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", - trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); + msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg); - return bits; + return 0; } -/* - * RX on uplink (indication to upper layer) +/* + * ready-to-send indication (to upper layer) */ -static int rx_rach_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, - float toa) +/* RTS for data frame */ +static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan) { - uint8_t chan_nr; - struct osmo_phsap_prim l1sap; - uint8_t ra; - int rc; + 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; - LOGP(DL1C, LOGL_NOTICE, "Received Access Burst on %s fn=%u toa=%.2f\n", - trx_chan_desc[chan].name, fn, toa); - - /* decode */ - rc = rach_decode(&ra, bits + 8 + 41, l1h->trx->bts->bsic); - if (rc) { - LOGP(DL1C, LOGL_NOTICE, "Received bad AB frame at fn=%u " - "(%u/51)\n", fn, fn % 51); - return 0; + if (!chan_nr) { + LOGP(DL1C, LOGL_FATAL, "RTS func for %s with non-existing " + "chan_nr %d\n", trx_chan_desc[chan].name, chan_nr); + return -ENODEV; } - /* compose primitive */ - /* generate prim */ - memset(&l1sap, 0, sizeof(l1sap)); - osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, - NULL); - l1sap.u.rach_ind.chan_nr = chan_nr; - l1sap.u.rach_ind.ra = ra; -#ifdef TA_TEST -#warning TIMING ADVANCE TEST-HACK IS ENABLED!!! - toa *= 10; -#endif - l1sap.u.rach_ind.acc_delay = (toa >= 0) ? toa : 0; - l1sap.u.rach_ind.fn = fn; + LOGP(DL1C, LOGL_INFO, "PH-RTS.ind: chan=%s chan_nr=0x%02x " + "link_id=0x%02x fn=%u ts=%u trx=%u\n", trx_chan_desc[chan].name, + chan_nr, link_id, fn, tn, l1t->trx->nr); - /* forward primitive */ - l1sap_up(l1h->trx, &l1sap); + /* generate prim */ + msg = l1sap_msgb_alloc(200); + if (!msg) + return -ENOMEM; + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, msg); + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.link_id = link_id; + l1sap->u.data.fn = fn; - return 0; + return l1sap_up(l1t->trx, l1sap); } -/*! \brief a single burst was received by the PHY, process it */ -static int rx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, - float toa) +static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, int facch) { - struct trx_chan_state *chan_state = &l1h->chan_states[tn][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; - float *toa_sum = &chan_state->toa_sum; - uint8_t *toa_num = &chan_state->toa_num; - uint8_t l2[GSM_MACBLOCK_LEN], l2_len; - int n_errors, n_bits_total; - int rc; - - /* handle rach, if handover rach detection is turned on */ - if (chan_state->ho_rach_detect == 1) - return rx_rach_fn(l1h, tn, fn, chan, bid, bits, rssi, toa); - - LOGP(DL1C, LOGL_DEBUG, "Data received %s fn=%u ts=%u trx=%u bid=%u\n", - trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); - - /* alloc burst memory, if not already */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, 464); - if (!*bursts_p) - return -ENOMEM; - } + 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; - /* clear burst & store frame number of first burst */ - if (bid == 0) { - memset(*bursts_p, 0, 464); - *mask = 0x0; - *first_fn = fn; - *rssi_sum = 0; - *rssi_num = 0; - *toa_sum = 0; - *toa_num = 0; - } + /* get data for RTS indication */ + chan_nr = trx_chan_desc[chan].chan_nr | tn; + link_id = trx_chan_desc[chan].link_id; - /* update mask + rssi */ - *mask |= (1 << bid); - *rssi_sum += rssi; - (*rssi_num)++; - *toa_sum += toa; - (*toa_num)++; - - /* copy burst to buffer of 4 bursts */ - burst = *bursts_p + bid * 116; - memcpy(burst, bits + 3, 58); - memcpy(burst + 58, bits + 87, 58); - - /* send burst information to loops process */ - if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) { - trx_loop_sacch_input(l1h, trx_chan_desc[chan].chan_nr | tn, - chan_state, rssi, toa); + if (!chan_nr) { + LOGP(DL1C, LOGL_FATAL, "RTS func for %s with non-existing " + "chan_nr %d\n", trx_chan_desc[chan].name, chan_nr); + return -ENODEV; } - /* wait until complete set of bursts */ - if (bid != 3) - return 0; - - /* check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGP(DL1C, LOGL_NOTICE, "Received incomplete data frame at " - "fn=%u (%u/%u) for %s\n", *first_fn, - (*first_fn) % l1h->mf_period[tn], l1h->mf_period[tn], - trx_chan_desc[chan].name); - - /* we require first burst to have correct FN */ - if (!(*mask & 0x1)) { - *mask = 0x0; - return 0; - } - } - *mask = 0x0; - - /* decode */ - rc = xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total); - if (rc) { - LOGP(DL1C, LOGL_NOTICE, "Received bad data frame at fn=%u " - "(%u/%u) for %s\n", *first_fn, - (*first_fn) % l1h->mf_period[tn], l1h->mf_period[tn], - trx_chan_desc[chan].name); - l2_len = 0; - } else - l2_len = GSM_MACBLOCK_LEN; - - /* Send uplnk measurement information to L2 */ - l1if_process_meas_res(l1h->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, - n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa_sum / *toa_num); - - return compose_ph_data_ind(l1h, tn, *first_fn, chan, l2, l2_len, *rssi_sum / *rssi_num); -} + LOGP(DL1C, LOGL_INFO, "TCH RTS.ind: chan=%s chan_nr=0x%02x " + "fn=%u ts=%u trx=%u\n", trx_chan_desc[chan].name, + chan_nr, fn, tn, l1t->trx->nr); -static int rx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, - float toa) -{ - struct trx_chan_state *chan_state = &l1h->chan_states[tn][chan]; - sbit_t *burst, **bursts_p = &chan_state->ul_bursts; - uint8_t *mask = &chan_state->ul_mask; - float *rssi_sum = &chan_state->rssi_sum; - uint8_t *rssi_num = &chan_state->rssi_num; - float *toa_sum = &chan_state->toa_sum; - uint8_t *toa_num = &chan_state->toa_num; - uint8_t l2[54+1]; - int n_errors, n_bits_total; - int rc; - - LOGP(DL1C, LOGL_DEBUG, "PDTCH received %s fn=%u ts=%u trx=%u bid=%u\n", - trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); - - /* alloc burst memory, if not already */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, 464); - if (!*bursts_p) + /* only send, if FACCH is selected */ + if (facch) { + /* generate prim */ + msg = l1sap_msgb_alloc(200); + if (!msg) return -ENOMEM; - } - - /* clear burst */ - if (bid == 0) { - memset(*bursts_p, 0, 464); - *mask = 0x0; - *rssi_sum = 0; - *rssi_num = 0; - *toa_sum = 0; - *toa_num = 0; - } - - /* update mask + rssi */ - *mask |= (1 << bid); - *rssi_sum += rssi; - (*rssi_num)++; - *toa_sum += toa; - (*toa_num)++; - - /* copy burst to buffer of 4 bursts */ - burst = *bursts_p + bid * 116; - memcpy(burst, bits + 3, 58); - memcpy(burst + 58, bits + 87, 58); - - /* wait until complete set of bursts */ - if (bid != 3) - return 0; - - /* check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGP(DL1C, LOGL_NOTICE, "Received incomplete PDTCH block " - "ending at fn=%u (%u/%u) for %s\n", fn, - fn % l1h->mf_period[tn], l1h->mf_period[tn], - trx_chan_desc[chan].name); - } - *mask = 0x0; - - /* decode */ - rc = pdtch_decode(l2 + 1, *bursts_p, NULL, &n_errors, &n_bits_total); - - /* Send uplnk measurement information to L2 */ - l1if_process_meas_res(l1h->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, - n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa_sum / *toa_num); + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, msg); + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.link_id = link_id; + l1sap->u.data.fn = fn; - if (rc <= 0) { - LOGP(DL1C, LOGL_NOTICE, "Received bad PDTCH block ending at " - "fn=%u (%u/%u) for %s\n", fn, fn % l1h->mf_period[tn], - l1h->mf_period[tn], trx_chan_desc[chan].name); - return 0; + rc = l1sap_up(l1t->trx, l1sap); } - l2[0] = 7; /* valid frame */ - - return compose_ph_data_ind(l1h, tn, (fn + GSM_HYPERFRAME - 3) % GSM_HYPERFRAME, chan, - l2, rc + 1, *rssi_sum / *rssi_num); -} - -static int rx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, - float toa) -{ - struct trx_chan_state *chan_state = &l1h->chan_states[tn][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, n_bits_total; - - /* handle rach, if handover rach detection is turned on */ - if (chan_state->ho_rach_detect == 1) - return rx_rach_fn(l1h, tn, fn, chan, bid, bits, rssi, toa); - - LOGP(DL1C, LOGL_DEBUG, "TCH/F received %s fn=%u ts=%u trx=%u bid=%u\n", - trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); - - /* alloc burst memory, if not already */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, 928); - if (!*bursts_p) + /* dont send, if TCH is in signalling only mode */ + if (l1ts->chan_state[chan].rsl_cmode != RSL_CMOD_SPD_SIGN) { + /* generate prim */ + msg = l1sap_msgb_alloc(200); + if (!msg) 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; - memcpy(burst, bits + 3, 58); - memcpy(burst + 58, bits + 87, 58); - - /* wait until complete set of bursts */ - if (bid != 3) - return 0; - - /* check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGP(DL1C, LOGL_NOTICE, "Received incomplete TCH frame ending " - "at fn=%u (%u/%u) for %s\n", fn, - fn % l1h->mf_period[tn], l1h->mf_period[tn], - trx_chan_desc[chan].name); - } - *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 = tch_fr_decode(tch_data, *bursts_p, 1, 0, &n_errors, &n_bits_total); - break; - case GSM48_CMODE_SPEECH_EFR: /* EFR */ - rc = 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. - */ - rc = tch_afs_decode(tch_data + 2, *bursts_p, - (((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); - if (rc) - trx_loop_amr_input(l1h, - trx_chan_desc[chan].chan_nr | tn, chan_state, - (float)n_errors/(float)n_bits_total); - amr = 2; /* we store tch_data + 2 header bytes */ - /* only good speech frames get rtp header */ - if (rc != GSM_MACBLOCK_LEN && rc >= 4) { - rc = amr_compose_payload(tch_data, - chan_state->codec[chan_state->ul_cmr], - chan_state->codec[chan_state->ul_ft], 0); - } - break; - default: - LOGP(DL1C, LOGL_ERROR, "TCH mode %u invalid, please fix!\n", - tch_mode); - return -EINVAL; - } - memcpy(*bursts_p, *bursts_p + 464, 464); - - /* Send uplnk measurement information to L2 */ - l1if_process_meas_res(l1h->trx, tn, fn, trx_chan_desc[chan].chan_nr|tn, - n_errors, n_bits_total, rssi, toa); - - /* Check if the frame is bad */ - if (rc < 0) { - LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " - "fn=%u for %s\n", fn, trx_chan_desc[chan].name); - goto bfi; - } - if (rc < 4) { - LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " - "fn=%u for %s with codec mode %d (out of range)\n", - fn, trx_chan_desc[chan].name, rc); - goto bfi; - } + l1sap = msgb_l1sap_prim(msg); + 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; - /* FACCH */ - if (rc == GSM_MACBLOCK_LEN) { - compose_ph_data_ind(l1h, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan, - tch_data + amr, GSM_MACBLOCK_LEN, rssi); -bfi: - if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { - /* indicate bad frame */ - switch (tch_mode) { - case GSM48_CMODE_SPEECH_V1: /* FR */ - memset(tch_data, 0, GSM_FR_BYTES); - rc = GSM_FR_BYTES; - break; - case GSM48_CMODE_SPEECH_EFR: /* EFR */ - memset(tch_data, 0, GSM_EFR_BYTES); - rc = GSM_EFR_BYTES; - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - rc = amr_compose_payload(tch_data, - chan_state->codec[chan_state->dl_cmr], - chan_state->codec[chan_state->dl_ft], - 1); - if (rc < 2) - break; - memset(tch_data + 2, 0, rc - 2); - break; - default: - LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, " - "please fix!\n"); - return -EINVAL; - } - } + return l1sap_up(l1t->trx, l1sap); } - if (rsl_cmode != RSL_CMOD_SPD_SPEECH) - return 0; - - /* TCH or BFI */ - return compose_tch_ind(l1h, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan, - tch_data, rc); + return rc; } -static int rx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, - float toa) +/* 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) { - struct trx_chan_state *chan_state = &l1h->chan_states[tn][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, n_bits_total; - - /* handle rach, if handover rach detection is turned on */ - if (chan_state->ho_rach_detect == 1) - return rx_rach_fn(l1h, tn, fn, chan, bid, bits, rssi, toa); - - LOGP(DL1C, LOGL_DEBUG, "TCH/H received %s fn=%u ts=%u trx=%u bid=%u\n", - trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); - - /* alloc 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; - memcpy(burst, bits + 3, 58); - memcpy(burst + 58, bits + 87, 58); - - /* wait until complete set of bursts */ - if (bid != 1) - return 0; - - /* check for complete set of bursts */ - if ((*mask & 0x3) != 0x3) { - LOGP(DL1C, LOGL_NOTICE, "Received incomplete TCH frame ending " - "at fn=%u (%u/%u) for %s\n", fn, - fn % l1h->mf_period[tn], l1h->mf_period[tn], - trx_chan_desc[chan].name); - } - *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); - 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 = tch_hr_decode(tch_data, *bursts_p, - (((fn + 26 - 10) % 26) >> 2) & 1, - &n_errors, &n_bits_total); - 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. - */ - rc = tch_ahs_decode(tch_data + 2, *bursts_p, - (((fn + 26 - 10) % 26) >> 2) & 1, - (((fn + 26 - 10) % 26) >> 2) & 1, chan_state->codec, - chan_state->codecs, &chan_state->ul_ft, - &chan_state->ul_cmr, &n_errors, &n_bits_total); - if (rc) - trx_loop_amr_input(l1h, - trx_chan_desc[chan].chan_nr | tn, chan_state, - (float)n_errors/(float)n_bits_total); - amr = 2; /* we store tch_data + 2 two */ - /* only good speech frames get rtp header */ - if (rc != GSM_MACBLOCK_LEN && rc >= 4) { - rc = amr_compose_payload(tch_data, - chan_state->codec[chan_state->ul_cmr], - chan_state->codec[chan_state->ul_ft], 0); - } - break; - default: - LOGP(DL1C, LOGL_ERROR, "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); - - /* Send uplnk measurement information to L2 */ - l1if_process_meas_res(l1h->trx, tn, fn, trx_chan_desc[chan].chan_nr|tn, - n_errors, n_bits_total, rssi, toa); - - /* Check if the frame is bad */ - if (rc < 0) { - LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " - "fn=%u for %s\n", fn, trx_chan_desc[chan].name); - goto bfi; - } - if (rc < 4) { - LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " - "fn=%u for %s with codec mode %d (out of range)\n", - fn, trx_chan_desc[chan].name, rc); - goto bfi; - } - - /* FACCH */ - if (rc == GSM_MACBLOCK_LEN) { - chan_state->ul_ongoing_facch = 1; - compose_ph_data_ind(l1h, tn, - (fn + GSM_HYPERFRAME - 10 - ((fn % 26) >= 19)) % GSM_HYPERFRAME, chan, - tch_data + amr, GSM_MACBLOCK_LEN, rssi); -bfi: - if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { - /* indicate bad frame */ - 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 = amr_compose_payload(tch_data, - chan_state->codec[chan_state->dl_cmr], - chan_state->codec[chan_state->dl_ft], - 1); - if (rc < 2) - break; - memset(tch_data + 2, 0, rc - 2); - break; - default: - LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, " - "please fix!\n"); - return -EINVAL; - } - } - } + /* TCH/F may include FACCH on every 4th burst */ + return rts_tch_common(l1t, tn, fn, chan, 1); +} - if (rsl_cmode != RSL_CMOD_SPD_SPEECH) - return 0; - /* 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 substracted to get correct - * start of frame. - */ - return compose_tch_ind(l1h, tn, - (fn + GSM_HYPERFRAME - 10 - ((fn%26)==19) - ((fn%26)==20)) % GSM_HYPERFRAME, - chan, tch_data, rc); +/* 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) +{ + /* 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); } - /* * multiframe structure */ @@ -2487,61 +1252,59 @@ static const struct trx_sched_multiframe trx_sched_multiframes[] = { */ /* set multiframe scheduler to given pchan */ -int trx_sched_set_pchan(struct trx_l1h *l1h, uint8_t tn, +int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn, enum gsm_phys_chan_config pchan) { + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); int i; - /* ignore disabled slots */ - if (!(l1h->config.slotmask & (1 << tn))) - return -ENOTSUP; - for (i = 0; i < ARRAY_SIZE(trx_sched_multiframes); i++) { if (trx_sched_multiframes[i].pchan == pchan && (trx_sched_multiframes[i].slotmask & (1 << tn))) { - l1h->mf_index[tn] = i; - l1h->mf_period[tn] = trx_sched_multiframes[i].period; - l1h->mf_frames[tn] = trx_sched_multiframes[i].frames; + 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, - l1h->trx->nr, tn); + l1t->trx->nr, tn); return 0; } } LOGP(DL1C, LOGL_NOTICE, "Failed to configuring multiframe " - "trx=%d ts=%d\n", l1h->trx->nr, tn); + "trx=%d ts=%d\n", l1t->trx->nr, tn); return -ENOTSUP; } /* setting all logical channels given attributes to active/inactive */ -int trx_sched_set_lchan(struct trx_l1h *l1h, uint8_t chan_nr, uint8_t link_id, +int trx_sched_set_lchan(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t link_id, int active) { 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 trx_chan_state *chan_state; /* 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 */ - if ((trx_sched_multiframes[l1h->mf_index[tn]].pchan + if ((trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH) != trx_chan_desc[i].pdch) continue; if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8) && trx_chan_desc[i].link_id == link_id) { - chan_state = &l1h->chan_states[tn][i]; 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, l1h->trx->nr, tn); + trx_chan_desc[i].name, l1t->trx->nr, tn); if (active) memset(chan_state, 0, sizeof(*chan_state)); chan_state->active = active; @@ -2554,41 +1317,42 @@ int trx_sched_set_lchan(struct trx_l1h *l1h, uint8_t chan_nr, uint8_t link_id, talloc_free(chan_state->ul_bursts); chan_state->ul_bursts = NULL; } + if (!active) + chan_state->ho_rach_detect = 0; } } /* disable handover detection (on deactivation) */ - if (l1h->ho_rach_detect[tn][ss]) { - l1h->ho_rach_detect[tn][ss] = 0; - trx_if_cmd_nohandover(l1h, tn, ss); - } + if (!active) + _sched_act_rach_det(l1t, tn, ss, 0); return rc; } /* setting all logical channels given attributes to active/inactive */ -int trx_sched_set_mode(struct trx_l1h *l1h, uint8_t chan_nr, uint8_t rsl_cmode, +int trx_sched_set_mode(struct l1sched_trx *l1t, 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) { 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 trx_chan_state *chan_state; + struct l1sched_chan_state *chan_state; /* no mode for PDCH */ - if (trx_sched_multiframes[l1h->mf_index[tn]].pchan == GSM_PCHAN_PDCH) + if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH) return 0; /* 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 = &l1h->chan_states[tn][i]; + 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, l1h->trx->nr, + handover, trx_chan_desc[i].name, l1t->trx->nr, tn); chan_state->rsl_cmode = rsl_cmode; chan_state->tch_mode = tch_mode; @@ -2616,28 +1380,23 @@ int trx_sched_set_mode(struct trx_l1h *l1h, uint8_t chan_nr, uint8_t rsl_cmode, * 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) */ - if (handover) { - l1h->ho_rach_detect[tn][ss] = 1; - trx_if_cmd_handover(l1h, tn, ss); - } else if (l1h->ho_rach_detect[tn][ss]) { - l1h->ho_rach_detect[tn][ss] = 0; - trx_if_cmd_nohandover(l1h, tn, ss); - } + _sched_act_rach_det(l1t, tn, ss, handover); return rc; } /* setting cipher on logical channels */ -int trx_sched_set_cipher(struct trx_l1h *l1h, uint8_t chan_nr, int downlink, +int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink, int algo, uint8_t *key, int key_len) { uint8_t tn = L1SAP_CHAN2TS(chan_nr); + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); int i; int rc = -EINVAL; - struct trx_chan_state *chan_state; + struct l1sched_chan_state *chan_state; /* no cipher for PDCH */ - if (trx_sched_multiframes[l1h->mf_index[tn]].pchan == GSM_PCHAN_PDCH) + if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH) return 0; /* no algorithm given means a5/0 */ @@ -2655,11 +1414,11 @@ int trx_sched_set_cipher(struct trx_l1h *l1h, uint8_t chan_nr, int downlink, if (trx_chan_desc[i].pdch) continue; if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8)) { - chan_state = &l1h->chan_states[tn][i]; + 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, l1h->trx->nr, tn); + trx_chan_desc[i].name, l1t->trx->nr, tn); if (downlink) { chan_state->dl_encr_algo = algo; memcpy(chan_state->dl_encr_key, key, key_len); @@ -2677,21 +1436,22 @@ int trx_sched_set_cipher(struct trx_l1h *l1h, uint8_t chan_nr, int downlink, } /* process ready-to-send */ -static int trx_sched_rts(struct trx_l1h *l1h, uint8_t tn, uint32_t fn) +int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, 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; enum trx_chan_type chan; /* no multiframe set */ - if (!l1h->mf_index[tn]) + if (!l1ts->mf_index) return 0; /* get frame from multiframe */ - period = l1h->mf_period[tn]; + period = l1ts->mf_period; offset = fn % period; - frame = l1h->mf_frames[tn] + offset; + frame = l1ts->mf_frames + offset; chan = frame->dl_chan; bid = frame->dl_bid; @@ -2707,49 +1467,50 @@ static int trx_sched_rts(struct trx_l1h *l1h, uint8_t tn, uint32_t fn) /* check if channel is active */ if (!trx_chan_desc[chan].auto_active - && !l1h->chan_states[tn][chan].active) + && !l1ts->chan_state[chan].active) return -EINVAL; - return func(l1h, tn, fn, frame->dl_chan); + return func(l1t, tn, fn, frame->dl_chan); } /* process downlink burst */ -static const ubit_t *trx_sched_dl_burst(struct trx_l1h *l1h, uint8_t tn, - uint32_t fn) +const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn) { + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *l1cs; const struct trx_sched_frame *frame; uint8_t offset, period, bid; trx_sched_dl_func *func; enum trx_chan_type chan; ubit_t *bits = NULL; - if (!l1h->mf_index[tn]) + if (!l1ts->mf_index) goto no_data; /* get frame from multiframe */ - period = l1h->mf_period[tn]; + period = l1ts->mf_period; offset = fn % period; - frame = l1h->mf_frames[tn] + offset; + frame = l1ts->mf_frames + offset; chan = frame->dl_chan; bid = frame->dl_bid; func = trx_chan_desc[chan].dl_fn; + l1cs = &l1ts->chan_state[chan]; + /* check if channel is active */ - if (!trx_chan_desc[chan].auto_active - && !l1h->chan_states[tn][chan].active) + if (!trx_chan_desc[chan].auto_active && !l1cs->active) goto no_data; /* get burst from function */ - bits = func(l1h, tn, fn, chan, bid); + bits = func(l1t, tn, fn, chan, bid); /* encrypt */ - if (bits && l1h->chan_states[tn][chan].dl_encr_algo) { + if (bits && l1cs->dl_encr_algo) { ubit_t ks[114]; int i; - osmo_a5(l1h->chan_states[tn][chan].dl_encr_algo, - l1h->chan_states[tn][chan].dl_encr_key, fn, ks, NULL); + osmo_a5(l1cs->dl_encr_algo, l1cs->dl_encr_key, fn, ks, NULL); for (i = 0; i < 57; i++) { bits[i + 3] ^= ks[i]; bits[i + 88] ^= ks[i + 57]; @@ -2758,7 +1519,7 @@ static const ubit_t *trx_sched_dl_burst(struct trx_l1h *l1h, uint8_t tn, no_data: /* in case of C0, we need a dummy burst to maintain RF power */ - if (bits == NULL && l1h->trx == l1h->trx->bts->c0) { + 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", @@ -2770,41 +1531,44 @@ if (0) if (chan != TRXC_IDLE) // hack } /* process uplink burst */ -int trx_sched_ul_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t current_fn, +int trx_sched_ul_burst(struct l1sched_trx *l1t, uint8_t tn, uint32_t current_fn, sbit_t *bits, int8_t rssi, float toa) { + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *l1cs; const struct trx_sched_frame *frame; uint8_t offset, period, bid; trx_sched_ul_func *func; enum trx_chan_type chan; uint32_t fn, elapsed; - if (!l1h->mf_index[tn]) + if (!l1ts->mf_index) return -EINVAL; /* calculate how many frames have been elapsed */ - elapsed = (current_fn + GSM_HYPERFRAME - l1h->mf_last_fn[tn]) % GSM_HYPERFRAME; + elapsed = (current_fn + GSM_HYPERFRAME - l1ts->mf_last_fn) % GSM_HYPERFRAME; /* start counting from last fn + 1, but only if not too many fn have * been elapsed */ if (elapsed < 10) - fn = (l1h->mf_last_fn[tn] + 1) % GSM_HYPERFRAME; + fn = (l1ts->mf_last_fn + 1) % GSM_HYPERFRAME; else fn = current_fn; while (42) { /* get frame from multiframe */ - period = l1h->mf_period[tn]; + period = l1ts->mf_period; offset = fn % period; - frame = l1h->mf_frames[tn] + offset; + frame = l1ts->mf_frames + offset; chan = frame->ul_chan; bid = frame->ul_bid; func = trx_chan_desc[chan].ul_fn; + l1cs = &l1ts->chan_state[chan]; + /* check if channel is active */ - if (!trx_chan_desc[chan].auto_active - && !l1h->chan_states[tn][chan].active) + if (!trx_chan_desc[chan].auto_active && !l1cs->active) goto next_frame; /* omit bursts which have no handler, like IDLE bursts */ @@ -2814,12 +1578,12 @@ int trx_sched_ul_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t current_fn, /* put burst to function */ if (fn == current_fn) { /* decrypt */ - if (bits && l1h->chan_states[tn][chan].ul_encr_algo) { + if (bits && l1cs->ul_encr_algo) { ubit_t ks[114]; int i; - osmo_a5(l1h->chan_states[tn][chan].ul_encr_algo, - l1h->chan_states[tn][chan].ul_encr_key, + osmo_a5(l1cs->ul_encr_algo, + l1cs->ul_encr_key, fn, NULL, ks); for (i = 0; i < 57; i++) { if (ks[i]) @@ -2829,13 +1593,12 @@ int trx_sched_ul_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t current_fn, } } - func(l1h, tn, fn, chan, bid, bits, rssi, toa); - } else if (chan != TRXC_RACH - && !l1h->chan_states[tn][chan].ho_rach_detect) { + func(l1t, tn, fn, chan, bid, bits, rssi, toa); + } else if (chan != TRXC_RACH && !l1cs->ho_rach_detect) { sbit_t spare[148]; memset(spare, 0, 148); - func(l1h, tn, fn, chan, bid, spare, -128, 0); + func(l1t, tn, fn, chan, bid, spare, -128, 0); } next_frame: @@ -2846,218 +1609,13 @@ next_frame: fn = (fn + 1) % GSM_HYPERFRAME; } - l1h->mf_last_fn[tn] = fn; - - return 0; -} - -/* schedule all frames of all TRX for given FN */ -static int trx_sched_fn(uint32_t fn) -{ - struct gsm_bts_trx *trx; - struct trx_l1h *l1h; - uint8_t tn; - const ubit_t *bits; - uint8_t gain; - - /* send time indication */ - l1if_mph_time_ind(bts, fn); - - /* advance frame number, so the transceiver has more time until - * it must be transmitted. */ - fn = (fn + trx_clock_advance) % GSM_HYPERFRAME; - - /* process every TRX */ - llist_for_each_entry(trx, &bts->trx_list, list) { - l1h = trx_l1h_hdl(trx); - - /* we don't schedule, if power is off */ - if (!l1h->config.poweron) - continue; - - /* process every TS of TRX */ - for (tn = 0; tn < TRX_NR_TS; tn++) { - /* ignore disabled slots */ - if (!(l1h->config.slotmask & (1 << tn))) - continue; - /* ready-to-send */ - trx_sched_rts(l1h, tn, - (fn + trx_rts_advance) % GSM_HYPERFRAME); - /* get burst for FN */ - bits = trx_sched_dl_burst(l1h, tn, fn); - if (!bits) { - /* if no bits, send no burst */ - continue; - } else - gain = 0; - trx_if_data(l1h, tn, fn, gain, bits); - } - } + l1ts->mf_last_fn = fn; return 0; } - -/* - * frame clock - */ - -#define FRAME_DURATION_uS 4615 -#define MAX_FN_SKEW 50 -#define TRX_LOSS_FRAMES 400 - -extern int quit; -/* this timer fires for every FN to be processed */ -static void trx_ctrl_timer_cb(void *data) +struct l1sched_ts *l1sched_trx_get_ts(struct l1sched_trx *l1t, uint8_t tn) { - struct timeval tv_now, *tv_clock = &transceiver_clock_tv; - int32_t elapsed; - - /* check if transceiver is still alive */ - if (transceiver_lost++ == TRX_LOSS_FRAMES) { - struct gsm_bts_trx *trx; - - LOGP(DL1C, LOGL_NOTICE, "No more clock from transceiver\n"); - -no_clock: - transceiver_available = 0; - - /* flush pending messages of transceiver */ - /* close all logical channels and reset timeslots */ - llist_for_each_entry(trx, &bts->trx_list, list) { - trx_if_flush(trx_l1h_hdl(trx)); - trx_sched_reset(trx_l1h_hdl(trx)); - if (trx->nr == 0) - trx_if_cmd_poweroff(trx_l1h_hdl(trx)); - } - - /* tell BSC */ - check_transceiver_availability(bts, 0); - - return; - } - - gettimeofday(&tv_now, NULL); - - elapsed = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000 - + (tv_now.tv_usec - tv_clock->tv_usec); - - /* if someone played with clock, or if the process stalled */ - if (elapsed > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed < 0) { - LOGP(DL1C, LOGL_NOTICE, "PC clock skew: elapsed uS %d\n", - elapsed); - goto no_clock; - } - - /* schedule next FN clock */ - while (elapsed > FRAME_DURATION_uS / 2) { - tv_clock->tv_usec += FRAME_DURATION_uS; - if (tv_clock->tv_usec >= 1000000) { - tv_clock->tv_sec++; - tv_clock->tv_usec -= 1000000; - } - transceiver_last_fn = (transceiver_last_fn + 1) % GSM_HYPERFRAME; - trx_sched_fn(transceiver_last_fn); - elapsed -= FRAME_DURATION_uS; - } - osmo_timer_schedule(&transceiver_clock_timer, 0, - FRAME_DURATION_uS - elapsed); + OSMO_ASSERT(tn < ARRAY_SIZE(l1t->ts)); + return &l1t->ts[tn]; } - - -/* receive clock from transceiver */ -int trx_sched_clock(uint32_t fn) -{ - struct timeval tv_now, *tv_clock = &transceiver_clock_tv; - int32_t elapsed; - int32_t elapsed_fn; - - if (quit) - return 0; - - /* reset lost counter */ - transceiver_lost = 0; - - gettimeofday(&tv_now, NULL); - - /* clock becomes valid */ - if (!transceiver_available) { - LOGP(DL1C, LOGL_NOTICE, "initial GSM clock received: fn=%u\n", - fn); - - transceiver_available = 1; - - /* start provisioning transceiver */ - l1if_provision_transceiver(bts); - - /* tell BSC */ - check_transceiver_availability(bts, 1); - -new_clock: - transceiver_last_fn = fn; - trx_sched_fn(transceiver_last_fn); - - /* schedule first FN clock */ - memcpy(tv_clock, &tv_now, sizeof(struct timeval)); - memset(&transceiver_clock_timer, 0, - sizeof(transceiver_clock_timer)); - transceiver_clock_timer.cb = trx_ctrl_timer_cb; - transceiver_clock_timer.data = bts; - osmo_timer_schedule(&transceiver_clock_timer, 0, - FRAME_DURATION_uS); - - return 0; - } - - osmo_timer_del(&transceiver_clock_timer); - - /* calculate elapsed time since last_fn */ - elapsed = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000 - + (tv_now.tv_usec - tv_clock->tv_usec); - - /* how much frames have been elapsed since last fn processed */ - elapsed_fn = (fn + GSM_HYPERFRAME - transceiver_last_fn) % GSM_HYPERFRAME; - if (elapsed_fn >= 135774) - elapsed_fn -= GSM_HYPERFRAME; - - /* check for max clock skew */ - if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) { - LOGP(DL1C, LOGL_NOTICE, "GSM clock skew: old fn=%u, " - "new fn=%u\n", transceiver_last_fn, fn); - goto new_clock; - } - - LOGP(DL1C, LOGL_INFO, "GSM clock jitter: %d\n", - elapsed_fn * FRAME_DURATION_uS - elapsed); - - /* too many frames have been processed already */ - if (elapsed_fn < 0) { - /* set clock to the time or last FN should have been - * transmitted. */ - tv_clock->tv_sec = tv_now.tv_sec; - tv_clock->tv_usec = tv_now.tv_usec + - (0 - elapsed_fn) * FRAME_DURATION_uS; - if (tv_clock->tv_usec >= 1000000) { - tv_clock->tv_sec++; - tv_clock->tv_usec -= 1000000; - } - /* set time to the time our next FN has to be transmitted */ - osmo_timer_schedule(&transceiver_clock_timer, 0, - FRAME_DURATION_uS * (1 - elapsed_fn)); - - return 0; - } - - /* transmit what we still need to transmit */ - while (fn != transceiver_last_fn) { - transceiver_last_fn = (transceiver_last_fn + 1) % GSM_HYPERFRAME; - trx_sched_fn(transceiver_last_fn); - } - - /* schedule next FN to be transmitted */ - memcpy(tv_clock, &tv_now, sizeof(struct timeval)); - osmo_timer_schedule(&transceiver_clock_timer, 0, FRAME_DURATION_uS); - - return 0; -} - diff --git a/src/osmo-bts-trx/Makefile.am b/src/osmo-bts-trx/Makefile.am index 26bc8e2e..0c0d2818 100644 --- a/src/osmo-bts-trx/Makefile.am +++ b/src/osmo-bts-trx/Makefile.am @@ -2,10 +2,10 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(ORTP_CFLAGS) LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS) -EXTRA_DIST = trx_if.h l1_if.h scheduler.h gsm0503_parity.h gsm0503_conv.h gsm0503_interleaving.h gsm0503_mapping.h gsm0503_coding.h gsm0503_tables.h loops.h amr.h +EXTRA_DIST = trx_if.h l1_if.h gsm0503_parity.h gsm0503_conv.h gsm0503_interleaving.h gsm0503_mapping.h gsm0503_coding.h gsm0503_tables.h loops.h amr.h bin_PROGRAMS = osmobts-trx -osmobts_trx_SOURCES = main.c trx_if.c l1_if.c scheduler.c trx_vty.c gsm0503_parity.c gsm0503_conv.c gsm0503_interleaving.c gsm0503_mapping.c gsm0503_coding.c gsm0503_tables.c loops.c amr.c -osmobts_trx_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) +osmobts_trx_SOURCES = main.c trx_if.c l1_if.c scheduler_trx.c trx_vty.c gsm0503_parity.c gsm0503_conv.c gsm0503_interleaving.c gsm0503_mapping.c gsm0503_coding.c gsm0503_tables.c loops.c amr.c +osmobts_trx_LDADD = $(top_builddir)/src/common/libbts.a $(top_builddir)/src/common/libl1sched.a $(LDADD) diff --git a/src/osmo-bts-trx/l1_if.c b/src/osmo-bts-trx/l1_if.c index a45ba05b..edd4b7bd 100644 --- a/src/osmo-bts-trx/l1_if.c +++ b/src/osmo-bts-trx/l1_if.c @@ -36,10 +36,10 @@ #include <osmo-bts/bts_model.h> #include <osmo-bts/amr.h> #include <osmo-bts/abis.h> +#include <osmo-bts/scheduler.h> #include "l1_if.h" #include "trx_if.h" -#include "scheduler.h" static const uint8_t transceiver_chan_types[_GSM_PCHAN_MAX] = { @@ -68,9 +68,10 @@ struct trx_l1h *l1if_open(struct gsm_bts_trx *trx) if (!l1h) return NULL; l1h->trx = trx; + l1h->l1s.trx = trx; trx->role_bts.l1h = l1h; - trx_sched_init(l1h); + trx_sched_init(&l1h->l1s); rc = trx_if_open(l1h); if (rc < 0) { @@ -89,7 +90,7 @@ err: void l1if_close(struct trx_l1h *l1h) { trx_if_close(l1h); - trx_sched_exit(l1h); + trx_sched_exit(&l1h->l1s); talloc_free(l1h); } @@ -267,7 +268,7 @@ int bts_model_trx_close(struct gsm_bts_trx *trx) enum gsm_phys_chan_config pchan = trx->ts[0].pchan; /* close all logical channels and reset timeslots */ - trx_sched_reset(l1h); + trx_sched_reset(&l1h->l1s); /* deactivate lchan for CCCH */ if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4) { @@ -374,8 +375,12 @@ static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts) l1if_provision_transceiver_trx(l1h); } + /* ignore disabled slots */ + if (!(l1h->config.slotmask & (1 << tn))) + return NM_NACK_RES_NOTAVAIL; + /* set physical channel */ - rc = trx_sched_set_pchan(l1h, tn, pchan); + rc = trx_sched_set_pchan(&l1h->l1s, tn, pchan); if (rc) return NM_NACK_RES_NOTAVAIL; @@ -413,17 +418,17 @@ static int l1if_set_ciphering(struct trx_l1h *l1h, struct gsm_lchan *lchan, if (!downlink) { /* set uplink */ - trx_sched_set_cipher(l1h, chan_nr, 0, lchan->encr.alg_id - 1, + trx_sched_set_cipher(&l1h->l1s, chan_nr, 0, lchan->encr.alg_id - 1, lchan->encr.key, lchan->encr.key_len); lchan->ciph_state = LCHAN_CIPH_RX_CONF; } else { /* set downlink and also set uplink, if not already */ if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) { - trx_sched_set_cipher(l1h, chan_nr, 0, + trx_sched_set_cipher(&l1h->l1s, chan_nr, 0, lchan->encr.alg_id - 1, lchan->encr.key, lchan->encr.key_len); } - trx_sched_set_cipher(l1h, chan_nr, 1, lchan->encr.alg_id - 1, + trx_sched_set_cipher(&l1h->l1s, chan_nr, 1, lchan->encr.alg_id - 1, lchan->encr.key, lchan->encr.key_len); lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; } @@ -510,12 +515,12 @@ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) if (!msg) break; /* put data into scheduler's queue */ - return trx_sched_ph_data_req(l1h, l1sap); + return trx_sched_ph_data_req(&l1h->l1s, l1sap); case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): if (!msg) break; /* put data into scheduler's queue */ - return trx_sched_tch_req(l1h, l1sap); + return trx_sched_tch_req(&l1h->l1s, l1sap); case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): switch (l1sap->u.info.type) { case PRIM_INFO_ACT_CIPH: @@ -542,11 +547,11 @@ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) break; } /* activate dedicated channel */ - trx_sched_set_lchan(l1h, chan_nr, 0x00, 1); + trx_sched_set_lchan(&l1h->l1s, chan_nr, 0x00, 1); /* activate associated channel */ - trx_sched_set_lchan(l1h, chan_nr, 0x40, 1); + trx_sched_set_lchan(&l1h->l1s, chan_nr, 0x40, 1); /* set mode */ - trx_sched_set_mode(l1h, chan_nr, + trx_sched_set_mode(&l1h->l1s, chan_nr, lchan->rsl_cmode, lchan->tch_mode, lchan->tch.amr_mr.num_modes, lchan->tch.amr_mr.bts_mode[0].mode, @@ -574,7 +579,7 @@ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) } if (l1sap->u.info.type == PRIM_INFO_MODIFY) { /* change mode */ - trx_sched_set_mode(l1h, chan_nr, + trx_sched_set_mode(&l1h->l1s, chan_nr, lchan->rsl_cmode, lchan->tch_mode, lchan->tch.amr_mr.num_modes, lchan->tch.amr_mr.bts_mode[0].mode, @@ -591,12 +596,12 @@ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) break; } /* deactivate associated channel */ - trx_sched_set_lchan(l1h, chan_nr, 0x40, 0); + trx_sched_set_lchan(&l1h->l1s, chan_nr, 0x40, 0); if (!l1sap->u.info.u.act_req.sacch_only) { /* set lchan inactive */ lchan_set_state(lchan, LCHAN_S_NONE); /* deactivate dedicated channel */ - trx_sched_set_lchan(l1h, chan_nr, 0x00, 0); + trx_sched_set_lchan(&l1h->l1s, chan_nr, 0x00, 0); /* confirm only on dedicated channel */ mph_info_chan_confirm(l1h, chan_nr, PRIM_INFO_DEACTIVATE, 0); diff --git a/src/osmo-bts-trx/l1_if.h b/src/osmo-bts-trx/l1_if.h index 2c672cb9..f4926876 100644 --- a/src/osmo-bts-trx/l1_if.h +++ b/src/osmo-bts-trx/l1_if.h @@ -1,109 +1,7 @@ #ifndef L1_IF_H_TRX #define L1_IF_H_TRX -/* These types define the different channels on a multiframe. - * Each channel has queues and can be activated individually. - */ -enum trx_chan_type { - TRXC_IDLE = 0, - TRXC_FCCH, - TRXC_SCH, - TRXC_BCCH, - TRXC_RACH, - TRXC_CCCH, - TRXC_TCHF, - TRXC_TCHH_0, - TRXC_TCHH_1, - TRXC_SDCCH4_0, - TRXC_SDCCH4_1, - TRXC_SDCCH4_2, - TRXC_SDCCH4_3, - TRXC_SDCCH8_0, - TRXC_SDCCH8_1, - TRXC_SDCCH8_2, - TRXC_SDCCH8_3, - TRXC_SDCCH8_4, - TRXC_SDCCH8_5, - TRXC_SDCCH8_6, - TRXC_SDCCH8_7, - TRXC_SACCHTF, - TRXC_SACCHTH_0, - TRXC_SACCHTH_1, - TRXC_SACCH4_0, - TRXC_SACCH4_1, - TRXC_SACCH4_2, - TRXC_SACCH4_3, - TRXC_SACCH8_0, - TRXC_SACCH8_1, - TRXC_SACCH8_2, - TRXC_SACCH8_3, - TRXC_SACCH8_4, - TRXC_SACCH8_5, - TRXC_SACCH8_6, - TRXC_SACCH8_7, - TRXC_PDTCH, - TRXC_PTCCH, - _TRX_CHAN_MAX -}; - -/* States each channel on a multiframe */ -struct trx_chan_state { - /* scheduler */ - uint8_t active; /* Channel is active */ - ubit_t *dl_bursts; /* burst buffer for TX */ - sbit_t *ul_bursts; /* burst buffer for RX */ - uint32_t ul_first_fn; /* fn of first burst */ - uint8_t ul_mask; /* mask of received bursts */ - - /* RSSI / TOA */ - uint8_t rssi_num; /* number of RSSI values */ - float rssi_sum; /* sum of RSSI values */ - uint8_t toa_num; /* number of TOA values */ - float toa_sum; /* sum of TOA values */ - - /* loss detection */ - uint8_t lost; /* (SACCH) loss detection */ - - /* mode */ - uint8_t rsl_cmode, tch_mode; /* mode for TCH channels */ - - /* AMR */ - uint8_t codec[4]; /* 4 possible codecs for amr */ - int codecs; /* number of possible codecs */ - float ber_sum; /* sum of bit error rates */ - int ber_num; /* number of bit error rates */ - uint8_t ul_ft; /* current uplink FT index */ - uint8_t dl_ft; /* current downlink FT index */ - uint8_t ul_cmr; /* current uplink CMR index */ - uint8_t dl_cmr; /* current downlink CMR index */ - uint8_t amr_loop; /* if AMR loop is enabled */ - - /* TCH/H */ - uint8_t dl_ongoing_facch; /* FACCH/H on downlink */ - uint8_t ul_ongoing_facch; /* FACCH/H on uplink */ - - /* encryption */ - int ul_encr_algo; /* A5/x encry algo downlink */ - int dl_encr_algo; /* A5/x encry algo uplink */ - int ul_encr_key_len; - int dl_encr_key_len; - uint8_t ul_encr_key[MAX_A5_KEY_LEN]; - uint8_t dl_encr_key[MAX_A5_KEY_LEN]; - - /* measurements */ - struct { - uint8_t clock; /* cyclic clock counter */ - int8_t rssi[32]; /* last RSSI values */ - int rssi_count; /* received RSSI values */ - int rssi_valid_count; /* number of stored value */ - int rssi_got_burst; /* any burst received so far */ - float toa_sum; /* sum of TOA values */ - int toa_num; /* number of TOA value */ - } meas; - - /* handover */ - uint8_t ho_rach_detect; /* if rach detection is on */ -}; +#include <osmo-bts/scheduler.h> struct trx_config { uint8_t poweron; /* poweron(1) or poweroff(0) */ @@ -152,16 +50,9 @@ struct trx_l1h { /* transceiver config */ struct trx_config config; - - uint8_t mf_index[TRX_NR_TS]; /* selected multiframe index */ - uint32_t mf_last_fn[TRX_NR_TS]; /* last received frame */ - uint8_t mf_period[TRX_NR_TS]; /* period of multiframe */ - const struct trx_sched_frame *mf_frames[TRX_NR_TS]; /* pointer to frame layout */ - - /* Channel states for all channels on all timeslots */ - struct trx_chan_state chan_states[TRX_NR_TS][_TRX_CHAN_MAX]; - struct llist_head dl_prims[TRX_NR_TS]; /* Queue primitves for TX */ uint8_t ho_rach_detect[TRX_NR_TS][TS_MAX_LCHAN]; + + struct l1sched_trx l1s; }; struct trx_l1h *l1if_open(struct gsm_bts_trx *trx); @@ -176,4 +67,10 @@ void l1if_fill_meas_res(struct osmo_phsap_prim *l1sap, uint8_t chan_nr, float ta int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr, int n_errors, int n_bits_total, float rssi, float toa); +static inline struct l1sched_trx *trx_l1sched_hdl(struct gsm_bts_trx *trx) +{ + struct trx_l1h *l1h = trx_l1h_hdl(trx); + return &l1h->l1s; +} + #endif /* L1_IF_H_TRX */ diff --git a/src/osmo-bts-trx/loops.c b/src/osmo-bts-trx/loops.c index 52ac170a..8070e802 100644 --- a/src/osmo-bts-trx/loops.c +++ b/src/osmo-bts-trx/loops.c @@ -33,7 +33,7 @@ #include "l1_if.h" #include "loops.h" -#define MS_PWR_DBM(lvl) ms_pwr_dbm(gsm_arfcn2band(l1h->config.arfcn), lvl) +#define MS_PWR_DBM(arfcn, lvl) ms_pwr_dbm(gsm_arfcn2band(arfcn), lvl) /* * MS Power loop @@ -42,11 +42,12 @@ int trx_ms_power_loop = 0; int8_t trx_target_rssi = -10; -static int ms_power_diff(struct trx_l1h *l1h, struct gsm_lchan *lchan, - uint8_t chan_nr, struct trx_chan_state *chan_state, int8_t diff) +static int ms_power_diff(struct gsm_lchan *lchan, uint8_t chan_nr, int8_t diff) { + struct gsm_bts_trx *trx = lchan->ts->trx; + uint16_t arfcn = trx->arfcn; int8_t new_power; - + new_power = lchan->ms_power - (diff >> 1); if (diff == 0) @@ -56,7 +57,7 @@ static int ms_power_diff(struct trx_l1h *l1h, struct gsm_lchan *lchan, new_power = 0; // FIXME: to go above 1W, we need to know classmark of MS - if (l1h->config.arfcn >= 512 && l1h->config.arfcn <= 885) { + if (arfcn >= 512 && arfcn <= 885) { if (new_power > 15) new_power = 15; } else { @@ -73,8 +74,8 @@ static int ms_power_diff(struct trx_l1h *l1h, struct gsm_lchan *lchan, if (lchan->ms_power == new_power) { LOGP(DLOOP, LOGL_INFO, "Keeping MS new_power of trx=%u " "chan_nr=0x%02x at control level %d (%d dBm)\n", - l1h->trx->nr, chan_nr, new_power, - MS_PWR_DBM(new_power)); + trx->nr, chan_nr, new_power, + MS_PWR_DBM(arfcn, new_power)); return 0; } @@ -82,15 +83,16 @@ static int ms_power_diff(struct trx_l1h *l1h, struct gsm_lchan *lchan, LOGP(DLOOP, LOGL_INFO, "%s MS new_power of trx=%u chan_nr=0x%02x from " "control level %d (%d dBm) to %d (%d dBm)\n", (diff > 0) ? "Raising" : "Lowering", - l1h->trx->nr, chan_nr, lchan->ms_power, - MS_PWR_DBM(lchan->ms_power), new_power, MS_PWR_DBM(new_power)); + trx->nr, chan_nr, lchan->ms_power, + MS_PWR_DBM(arfcn, lchan->ms_power), new_power, + MS_PWR_DBM(arfcn, new_power)); lchan->ms_power = new_power; return 0; } -static int ms_power_val(struct trx_chan_state *chan_state, int8_t rssi) +static int ms_power_val(struct l1sched_chan_state *chan_state, int8_t rssi) { /* ignore inserted dummy frames, treat as lost frames */ if (rssi < -127) @@ -112,9 +114,10 @@ static int ms_power_val(struct trx_chan_state *chan_state, int8_t rssi) return 0; } -static int ms_power_clock(struct trx_l1h *l1h, struct gsm_lchan *lchan, - uint8_t chan_nr, struct trx_chan_state *chan_state) +static int ms_power_clock(struct gsm_lchan *lchan, + uint8_t chan_nr, struct l1sched_chan_state *chan_state) { + struct gsm_bts_trx *trx = lchan->ts->trx; int rssi; int i; @@ -134,9 +137,8 @@ static int ms_power_clock(struct trx_l1h *l1h, struct gsm_lchan *lchan, if (chan_state->meas.rssi_count == 0) { LOGP(DLOOP, LOGL_NOTICE, "LOST SACCH frame of trx=%u " "chan_nr=0x%02x, so we raise MS power\n", - l1h->trx->nr, chan_nr); - return ms_power_diff(l1h, lchan, chan_nr, chan_state, - MS_RAISE_MAX); + trx->nr, chan_nr); + return ms_power_diff(lchan, chan_nr, MS_RAISE_MAX); } /* reset total counter */ @@ -157,9 +159,10 @@ static int ms_power_clock(struct trx_l1h *l1h, struct gsm_lchan *lchan, /* change RSSI */ LOGP(DLOOP, LOGL_DEBUG, "Lowest RSSI: %d Target RSSI: %d Current " "MS power: %d (%d dBm) of trx=%u chan_nr=0x%02x\n", rssi, - trx_target_rssi, lchan->ms_power, MS_PWR_DBM(lchan->ms_power), - l1h->trx->nr, chan_nr); - ms_power_diff(l1h, lchan, chan_nr, chan_state, trx_target_rssi - rssi); + trx_target_rssi, lchan->ms_power, + MS_PWR_DBM(trx->arfcn, lchan->ms_power), + trx->nr, chan_nr); + ms_power_diff(lchan, chan_nr, trx_target_rssi - rssi); return 0; } @@ -171,9 +174,11 @@ static int ms_power_clock(struct trx_l1h *l1h, struct gsm_lchan *lchan, int trx_ta_loop = 1; -int ta_val(struct trx_l1h *l1h, struct gsm_lchan *lchan, uint8_t chan_nr, - struct trx_chan_state *chan_state, float toa) +int ta_val(struct gsm_lchan *lchan, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, float toa) { + struct gsm_bts_trx *trx = lchan->ts->trx; + /* check if the current L1 header acks to the current ordered TA */ if (lchan->meas.l1_info[1] != lchan->rqd_ta) return 0; @@ -190,19 +195,19 @@ int ta_val(struct trx_l1h *l1h, struct gsm_lchan *lchan, uint8_t chan_nr, if (toa < -0.9F && lchan->rqd_ta > 0) { LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too " "early (%.2f), now lowering TA from %d to %d\n", - l1h->trx->nr, chan_nr, toa, lchan->rqd_ta, + trx->nr, chan_nr, toa, lchan->rqd_ta, lchan->rqd_ta - 1); lchan->rqd_ta--; } else if (toa > 0.9F && lchan->rqd_ta < 63) { LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too " "late (%.2f), now raising TA from %d to %d\n", - l1h->trx->nr, chan_nr, toa, lchan->rqd_ta, + trx->nr, chan_nr, toa, lchan->rqd_ta, lchan->rqd_ta + 1); lchan->rqd_ta++; } else LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is " "correct (%.2f), keeping current TA of %d\n", - l1h->trx->nr, chan_nr, toa, lchan->rqd_ta); + trx->nr, chan_nr, toa, lchan->rqd_ta); chan_state->meas.toa_num = 0; chan_state->meas.toa_sum = 0; @@ -210,29 +215,29 @@ int ta_val(struct trx_l1h *l1h, struct gsm_lchan *lchan, uint8_t chan_nr, return 0; } -int trx_loop_sacch_input(struct trx_l1h *l1h, uint8_t chan_nr, - struct trx_chan_state *chan_state, int8_t rssi, float toa) +int trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, int8_t rssi, float toa) { - struct gsm_lchan *lchan = &l1h->trx->ts[L1SAP_CHAN2TS(chan_nr)] + struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)] .lchan[l1sap_chan2ss(chan_nr)]; if (trx_ms_power_loop) ms_power_val(chan_state, rssi); if (trx_ta_loop) - ta_val(l1h, lchan, chan_nr, chan_state, toa); + ta_val(lchan, chan_nr, chan_state, toa); return 0; } -int trx_loop_sacch_clock(struct trx_l1h *l1h, uint8_t chan_nr, - struct trx_chan_state *chan_state) +int trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state) { - struct gsm_lchan *lchan = &l1h->trx->ts[L1SAP_CHAN2TS(chan_nr)] + struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)] .lchan[l1sap_chan2ss(chan_nr)]; if (trx_ms_power_loop) - ms_power_clock(l1h, lchan, chan_nr, chan_state); + ms_power_clock(lchan, chan_nr, chan_state); /* count the number of SACCH clocks */ chan_state->meas.clock++; @@ -240,10 +245,11 @@ int trx_loop_sacch_clock(struct trx_l1h *l1h, uint8_t chan_nr, return 0; } -int trx_loop_amr_input(struct trx_l1h *l1h, uint8_t chan_nr, - struct trx_chan_state *chan_state, float ber) +int trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, float ber) { - struct gsm_lchan *lchan = &l1h->trx->ts[L1SAP_CHAN2TS(chan_nr)] + struct gsm_bts_trx *trx = l1t->trx; + struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)] .lchan[l1sap_chan2ss(chan_nr)]; int c_i; @@ -280,7 +286,7 @@ int trx_loop_amr_input(struct trx_l1h *l1h, uint8_t chan_nr, LOGP(DLOOP, LOGL_DEBUG, "Current bit error rate (BER) %.6f " "codec id %d of trx=%u chan_nr=0x%02x\n", ber, - chan_state->ul_ft, l1h->trx->nr, chan_nr); + chan_state->ul_ft, trx->nr, chan_nr); /* degrade */ if (chan_state->dl_cmr > 0) { @@ -290,7 +296,7 @@ int trx_loop_amr_input(struct trx_l1h *l1h, uint8_t chan_nr, LOGP(DLOOP, LOGL_DEBUG, "Degrading due to BER %.6f " "from codec id %d to %d of trx=%u " "chan_nr=0x%02x\n", ber, chan_state->dl_cmr, - chan_state->dl_cmr - 1, l1h->trx->nr, chan_nr); + chan_state->dl_cmr - 1, trx->nr, chan_nr); chan_state->dl_cmr--; } @@ -306,7 +312,7 @@ int trx_loop_amr_input(struct trx_l1h *l1h, uint8_t chan_nr, LOGP(DLOOP, LOGL_DEBUG, "Upgrading due to BER %.6f " "from codec id %d to %d of trx=%u " "chan_nr=0x%02x\n", ber, chan_state->dl_cmr, - chan_state->dl_cmr + 1, l1h->trx->nr, chan_nr); + chan_state->dl_cmr + 1, trx->nr, chan_nr); chan_state->dl_cmr++; } @@ -316,7 +322,7 @@ int trx_loop_amr_input(struct trx_l1h *l1h, uint8_t chan_nr, return 0; } -int trx_loop_amr_set(struct trx_chan_state *chan_state, int loop) +int trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop) { if (chan_state->amr_loop && !loop) { chan_state->amr_loop = 0; @@ -336,4 +342,3 @@ int trx_loop_amr_set(struct trx_chan_state *chan_state, int loop) return 0; } - diff --git a/src/osmo-bts-trx/loops.h b/src/osmo-bts-trx/loops.h index 27b0ef23..613d2d01 100644 --- a/src/osmo-bts-trx/loops.h +++ b/src/osmo-bts-trx/loops.h @@ -17,15 +17,15 @@ extern int trx_ms_power_loop; extern int8_t trx_target_rssi; extern int trx_ta_loop; -int trx_loop_sacch_input(struct trx_l1h *l1h, uint8_t chan_nr, - struct trx_chan_state *chan_state, int8_t rssi, float toa); +int trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, int8_t rssi, float toa); -int trx_loop_sacch_clock(struct trx_l1h *l1h, uint8_t chan_nr, - struct trx_chan_state *chan_state); +int trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state); -int trx_loop_amr_input(struct trx_l1h *l1h, uint8_t chan_nr, - struct trx_chan_state *chan_state, float ber); +int trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, float ber); -int trx_loop_amr_set(struct trx_chan_state *chan_state, int loop); +int trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop); #endif /* _TRX_LOOPS_H */ diff --git a/src/osmo-bts-trx/main.c b/src/osmo-bts-trx/main.c index 6c2dabf7..aa6987c5 100644 --- a/src/osmo-bts-trx/main.c +++ b/src/osmo-bts-trx/main.c @@ -53,10 +53,10 @@ #include <osmo-bts/pcu_if.h> #include <osmo-bts/l1sap.h> #include <osmo-bts/control_if.h> +#include <osmo-bts/scheduler.h> #include "l1_if.h" #include "trx_if.h" -#include "scheduler.h" int bts_model_init(struct gsm_bts *bts) { diff --git a/src/osmo-bts-trx/scheduler.h b/src/osmo-bts-trx/scheduler.h deleted file mode 100644 index 3e306937..00000000 --- a/src/osmo-bts-trx/scheduler.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef TRX_SCHEDULER_H -#define TRX_SCHEDULER_H - -/*! \brief how many frame numbers in advance we should send bursts to PHY */ -extern uint32_t trx_clock_advance; -/*! \brief advance RTS.ind to L2 by that many clocks */ -extern uint32_t trx_rts_advance; -/*! \brief last frame number as received from PHY */ -extern uint32_t transceiver_last_fn; - - -/*! \brief Initialize the scheudler data structures */ -int trx_sched_init(struct trx_l1h *l1h); - -/*! \brief De-initialize the scheudler data structures */ -void trx_sched_exit(struct trx_l1h *l1h); - -/*! \brief Handle a PH-DATA.req from L2 down to L1 */ -int trx_sched_ph_data_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap); - -/*! \brief Handle a PH-TCH.req from L2 down to L1 */ -int trx_sched_tch_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap); - -/*! \brief PHY informs us of new (current) GSM freme nunmber */ -int trx_sched_clock(uint32_t fn); - -/*! \brief handle an UL burst received by PHY */ -int trx_sched_ul_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, - sbit_t *bits, int8_t rssi, float toa); - -/*! \brief set multiframe scheduler to given physical channel config */ -int trx_sched_set_pchan(struct trx_l1h *l1h, uint8_t tn, - enum gsm_phys_chan_config pchan); - -/*! \brief set all matching logical channels active/inactive */ -int trx_sched_set_lchan(struct trx_l1h *l1h, uint8_t chan_nr, uint8_t link_id, - int active); - -/*! \brief set mode of all matching logical channels to given mode(s) */ -int trx_sched_set_mode(struct trx_l1h *l1h, 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_codec, - uint8_t handover); - -/*! \brief set ciphering on given logical channels */ -int trx_sched_set_cipher(struct trx_l1h *l1h, uint8_t chan_nr, int downlink, - int algo, uint8_t *key, int key_len); - -/* \brief close all logical channels and reset timeslots */ -void trx_sched_reset(struct trx_l1h *l1h); - -#endif /* TRX_SCHEDULER_H */ diff --git a/src/osmo-bts-trx/scheduler_trx.c b/src/osmo-bts-trx/scheduler_trx.c new file mode 100644 index 00000000..ac9212ba --- /dev/null +++ b/src/osmo-bts-trx/scheduler_trx.c @@ -0,0 +1,1470 @@ +/* Scheduler worker functiosn for OsmoBTS-TRX */ + +/* (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> + * + * 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 <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/bits.h> +#include <osmocom/gsm/a5.h> + +#include <osmocom/netif/rtp.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/scheduler.h> +#include <osmo-bts/scheduler_backend.h> + +#include "l1_if.h" +#include "gsm0503_coding.h" +#include "trx_if.h" +#include "loops.h" +#include "amr.h" + +extern void *tall_bts_ctx; + +/* clock states */ +static uint32_t transceiver_lost; +uint32_t transceiver_last_fn; +static struct timeval transceiver_clock_tv; +static struct osmo_timer_list transceiver_clock_timer; + +/* clock advance for the transceiver */ +uint32_t trx_clock_advance = 20; + +/* advance RTS to give some time for data processing. (especially PCU) */ +uint32_t trx_rts_advance = 5; /* about 20ms */ + +/* Enable this to multiply TOA of RACH by 10. + * This is usefull to check tenth of timing advances with RSSI test tool. + * Note that regular phones will not work when using this test! */ +//#define TA_TEST + + +/* + * TX on downlink + */ + +/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */ +ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", + trx_chan_desc[chan].name, fn, tn, l1t->trx->nr); + + return NULL; +} + +ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", + trx_chan_desc[chan].name, fn, tn, l1t->trx->nr); + + /* BURST BYPASS */ + + return (ubit_t *) _sched_fcch_burst; +} + +ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + static ubit_t bits[148], burst[78]; + uint8_t sb_info[4]; + struct gsm_time t; + uint8_t t3p, bsic; + + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", + trx_chan_desc[chan].name, fn, tn, l1t->trx->nr); + + /* 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 */ + 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); + + return bits; +} + +ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; + uint8_t link_id = trx_chan_desc[chan].link_id; + uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; + struct msgb *msg = NULL; /* make GCC happy */ + ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts; + static ubit_t bits[148]; + + /* send burst, if we already got a frame */ + if (bid > 0) { + if (!*bursts_p) + return NULL; + goto send_burst; + } + + /* send clock information to loops process */ + if (L1SAP_IS_LINK_SACCH(link_id)) + trx_loop_sacch_clock(l1t, chan_nr, &l1ts->chan_state[chan]); + + /* get mac block from queue */ + msg = _sched_dequeue_prim(l1t, tn, fn, chan); + if (msg) + goto got_msg; + + LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " + "trx=%u ts=%u at fn=%u to transmit.\n", + trx_chan_desc[chan].name, l1t->trx->nr, tn, fn); + +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) { + LOGP(DL1C, LOGL_FATAL, "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) > 1) { + /* TODO: Should we pass old TOA here? Otherwise we risk + * unnecessary decreasing TA */ + + /* Send uplnk measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, + 456, 456, -110, 0); + + _sched_compose_ph_data_ind(l1t, tn, 0, chan, NULL, 0, -110); + } + } + + /* alloc burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 464); + if (!*bursts_p) + return NULL; + } + + /* encode bursts */ + 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); + + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", + trx_chan_desc[chan].name, fn, tn, l1t->trx->nr, bid); + + return bits; +} + +ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + 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[148]; + int rc; + + /* 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; + + LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " + "trx=%u ts=%u at fn=%u to transmit.\n", + trx_chan_desc[chan].name, l1t->trx->nr, tn, fn); + +no_msg: + /* free burst memory */ + if (*bursts_p) { + talloc_free(*bursts_p); + *bursts_p = NULL; + } + return NULL; + +got_msg: + /* BURST BYPASS */ + + /* alloc burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 464); + if (!*bursts_p) + return NULL; + } + + /* encode bursts */ + rc = pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h); + + /* check validity of message */ + if (rc) { + LOGP(DL1C, LOGL_FATAL, "Prim invalid length, please FIX! " + "(len=%d)\n", rc); + /* free message */ + msgb_free(msg); + goto no_msg; + } + + /* 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); + + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", + trx_chan_desc[chan].name, fn, tn, l1t->trx->nr, bid); + + return bits; +} + +static 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, int codec_mode_request) +{ + 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; + + /* handle loss detection of received TCH frames */ + if (rsl_cmode == RSL_CMOD_SPD_SPEECH + && ++(chan_state->lost) > 5) { + uint8_t tch_data[GSM_FR_BYTES]; + int len; + + LOGP(DL1C, LOGL_NOTICE, "Missing TCH bursts detected, sending " + "BFI for %s\n", trx_chan_desc[chan].name); + + /* 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 = amr_compose_payload(tch_data, + chan_state->codec[chan_state->dl_cmr], + chan_state->codec[chan_state->dl_ft], 1); + if (len < 2) + break; + memset(tch_data + 2, 0, len - 2); + _sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len); + break; + default: +inval_mode1: + LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, please " + "fix!\n"); + len = 0; + } + if (len) + _sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len); + } + + /* 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) { + LOGP(DL1C, LOGL_FATAL, "TCH twice, " + "please FIX! "); + msgb_free(msg2); + } else + msg_facch = msg2; + } + } else { + msg_facch = msg1; + if (msg2) { + l1sap = msgb_l1sap_prim(msg2); + if (l1sap->oph.primitive != PRIM_TCH) { + LOGP(DL1C, LOGL_FATAL, "FACCH twice, " + "please FIX! "); + 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) { + LOGP(DL1C, LOGL_FATAL, "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 bfi, cmr_codec, ft_codec; + int cmr, ft, i; + + if (rsl_cmode != RSL_CMOD_SPD_SPEECH) { + LOGP(DL1C, LOGL_NOTICE, "%s Dropping speech frame, " + "because we are not in speech mode trx=%u " + "ts=%u at fn=%u.\n", trx_chan_desc[chan].name, + l1t->trx->nr, tn, fn); + goto free_bad_msg; + } + + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR / HR */ + if (chan != TRXC_TCHF) { /* HR */ + len = 15; + if (msgb_l2len(msg_tch) >= 1 + && (msg_tch->l2h[0] & 0xf0) != 0x00) { + LOGP(DL1C, LOGL_NOTICE, "%s " + "Transmitting 'bad " + "HR frame' trx=%u ts=%u at " + "fn=%u.\n", + trx_chan_desc[chan].name, + l1t->trx->nr, tn, fn); + goto free_bad_msg; + } + break; + } + len = GSM_FR_BYTES; + if (msgb_l2len(msg_tch) >= 1 + && (msg_tch->l2h[0] >> 4) != 0xd) { + LOGP(DL1C, LOGL_NOTICE, "%s Transmitting 'bad " + "FR frame' trx=%u ts=%u at fn=%u.\n", + trx_chan_desc[chan].name, + l1t->trx->nr, tn, fn); + goto free_bad_msg; + } + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + if (chan != TRXC_TCHF) + goto inval_mode2; + len = GSM_EFR_BYTES; + if (msgb_l2len(msg_tch) >= 1 + && (msg_tch->l2h[0] >> 4) != 0xc) { + LOGP(DL1C, LOGL_NOTICE, "%s Transmitting 'bad " + "EFR frame' trx=%u ts=%u at fn=%u.\n", + trx_chan_desc[chan].name, + l1t->trx->nr, tn, fn); + goto free_bad_msg; + } + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + len = amr_decompose_payload(msg_tch->l2h, + msgb_l2len(msg_tch), &cmr_codec, &ft_codec, + &bfi); + 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) { + LOGP(DL1C, LOGL_ERROR, "%s Codec (FT = %d) " + " of RTP frame not in list. " + "trx=%u ts=%u\n", + trx_chan_desc[chan].name, ft_codec, + l1t->trx->nr, tn); + goto free_bad_msg; + } + if (codec_mode_request && chan_state->dl_ft != ft) { + LOGP(DL1C, LOGL_NOTICE, "%s Codec (FT = %d) " + " of RTP cannot be changed now, but in " + "next frame. trx=%u ts=%u\n", + trx_chan_desc[chan].name, ft_codec, + l1t->trx->nr, tn); + goto free_bad_msg; + } + chan_state->dl_ft = ft; + if (bfi) { + LOGP(DL1C, LOGL_NOTICE, "%s Transmitting 'bad " + "AMR frame' trx=%u ts=%u at fn=%u.\n", + trx_chan_desc[chan].name, + l1t->trx->nr, tn, fn); + goto free_bad_msg; + } + break; + default: +inval_mode2: + LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, please " + "fix!\n"); + goto free_bad_msg; + } + if (len < 0) { + LOGP(DL1C, LOGL_ERROR, "Cannot send invalid AMR " + "payload\n"); + goto free_bad_msg; + } + if (msgb_l2len(msg_tch) != len) { + LOGP(DL1C, LOGL_ERROR, "Cannot send payload with " + "invalid length! (expecing %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; +} + +ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + 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[148]; + + /* 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, + (((fn + 4) % 26) >> 2) & 1); + + /* BURST BYPASS */ + + /* alloc 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) { + LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " + "trx=%u ts=%u at fn=%u to transmit.\n", + trx_chan_desc[chan].name, l1t->trx->nr, tn, fn); + goto send_burst; + } + + /* encode bursts (priorize FACCH) */ + if (msg_facch) + 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. + */ + tch_afs_encode(*bursts_p, msg_tch->l2h + 2, + msgb_l2len(msg_tch) - 2, (((fn + 4) % 26) >> 2) & 1, + chan_state->codec, chan_state->codecs, + chan_state->dl_ft, + chan_state->dl_cmr); + else + 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); + + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", + trx_chan_desc[chan].name, fn, tn, l1t->trx->nr, bid); + + return bits; +} + +ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + 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[148]; + + /* 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, + (((fn + 4) % 26) >> 2) & 1); + + /* check for FACCH alignment */ + if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) { + LOGP(DL1C, LOGL_ERROR, "%s Cannot transmit FACCH starting on " + "even frames, please fix RTS!\n", + trx_chan_desc[chan].name); + msgb_free(msg_facch); + msg_facch = NULL; + } + + /* BURST BYPASS */ + + /* alloc 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) { + LOGP(DL1C, LOGL_INFO, "%s has not been served !! No prim for " + "trx=%u ts=%u at fn=%u to transmit.\n", + trx_chan_desc[chan].name, l1t->trx->nr, tn, fn); + goto send_burst; + } + + /* encode bursts (priorize FACCH) */ + if (msg_facch) { + 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. */ + tch_ahs_encode(*bursts_p, msg_tch->l2h + 2, + msgb_l2len(msg_tch) - 2, (((fn + 4) % 26) >> 2) & 1, + chan_state->codec, chan_state->codecs, + chan_state->dl_ft, + chan_state->dl_cmr); + else + 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); + + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", + trx_chan_desc[chan].name, fn, tn, l1t->trx->nr, bid); + + return bits; +} + + +/* + * RX on uplink (indication to upper layer) + */ + +int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, + float toa) +{ + uint8_t chan_nr; + struct osmo_phsap_prim l1sap; + uint8_t ra; + int rc; + + chan_nr = trx_chan_desc[chan].chan_nr | tn; + + LOGP(DL1C, LOGL_NOTICE, "Received Access Burst on %s fn=%u toa=%.2f\n", + trx_chan_desc[chan].name, fn, toa); + + /* decode */ + rc = rach_decode(&ra, bits + 8 + 41, l1t->trx->bts->bsic); + if (rc) { + LOGP(DL1C, LOGL_NOTICE, "Received bad AB frame at fn=%u " + "(%u/51)\n", fn, fn % 51); + return 0; + } + + /* compose primitive */ + /* generate prim */ + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, + NULL); + l1sap.u.rach_ind.chan_nr = chan_nr; + l1sap.u.rach_ind.ra = ra; +#ifdef TA_TEST +#warning TIMING ADVANCE TEST-HACK IS ENABLED!!! + toa *= 10; +#endif + l1sap.u.rach_ind.acc_delay = (toa >= 0) ? toa : 0; + l1sap.u.rach_ind.fn = fn; + + /* forward primitive */ + l1sap_up(l1t->trx, &l1sap); + + return 0; +} + +/*! \brief a single burst was received by the PHY, process it */ +int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, + float toa) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, 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; + float *toa_sum = &chan_state->toa_sum; + uint8_t *toa_num = &chan_state->toa_num; + uint8_t l2[GSM_MACBLOCK_LEN], l2_len; + int n_errors, n_bits_total; + int rc; + + /* handle rach, if handover rach detection is turned on */ + if (chan_state->ho_rach_detect == 1) + return rx_rach_fn(l1t, tn, fn, chan, bid, bits, rssi, toa); + + LOGP(DL1C, LOGL_DEBUG, "Data received %s fn=%u ts=%u trx=%u bid=%u\n", + trx_chan_desc[chan].name, fn, tn, l1t->trx->nr, bid); + + /* alloc 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 = fn; + *rssi_sum = 0; + *rssi_num = 0; + *toa_sum = 0; + *toa_num = 0; + } + + /* update mask + rssi */ + *mask |= (1 << bid); + *rssi_sum += rssi; + (*rssi_num)++; + *toa_sum += toa; + (*toa_num)++; + + /* copy burst to buffer of 4 bursts */ + burst = *bursts_p + bid * 116; + memcpy(burst, bits + 3, 58); + memcpy(burst + 58, bits + 87, 58); + + /* send burst information to loops process */ + if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) { + trx_loop_sacch_input(l1t, trx_chan_desc[chan].chan_nr | tn, + chan_state, rssi, toa); + } + + /* wait until complete set of bursts */ + if (bid != 3) + return 0; + + /* check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGP(DL1C, LOGL_NOTICE, "Received incomplete data frame at " + "fn=%u (%u/%u) for %s\n", *first_fn, + (*first_fn) % l1ts->mf_period, l1ts->mf_period, + trx_chan_desc[chan].name); + + /* we require first burst to have correct FN */ + if (!(*mask & 0x1)) { + *mask = 0x0; + return 0; + } + } + *mask = 0x0; + + /* decode */ + rc = xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total); + if (rc) { + LOGP(DL1C, LOGL_NOTICE, "Received bad data frame at fn=%u " + "(%u/%u) for %s\n", *first_fn, + (*first_fn) % l1ts->mf_period, l1ts->mf_period, + trx_chan_desc[chan].name); + l2_len = 0; + } else + l2_len = GSM_MACBLOCK_LEN; + + /* Send uplnk measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, + n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa_sum / *toa_num); + + return _sched_compose_ph_data_ind(l1t, tn, *first_fn, chan, l2, l2_len, *rssi_sum / *rssi_num); +} + +int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, + float toa) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, 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; + float *rssi_sum = &chan_state->rssi_sum; + uint8_t *rssi_num = &chan_state->rssi_num; + float *toa_sum = &chan_state->toa_sum; + uint8_t *toa_num = &chan_state->toa_num; + uint8_t l2[54+1]; + int n_errors, n_bits_total; + int rc; + + LOGP(DL1C, LOGL_DEBUG, "PDTCH received %s fn=%u ts=%u trx=%u bid=%u\n", + trx_chan_desc[chan].name, fn, tn, l1t->trx->nr, bid); + + /* alloc burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 464); + if (!*bursts_p) + return -ENOMEM; + } + + /* clear burst */ + if (bid == 0) { + memset(*bursts_p, 0, 464); + *mask = 0x0; + *rssi_sum = 0; + *rssi_num = 0; + *toa_sum = 0; + *toa_num = 0; + } + + /* update mask + rssi */ + *mask |= (1 << bid); + *rssi_sum += rssi; + (*rssi_num)++; + *toa_sum += toa; + (*toa_num)++; + + /* copy burst to buffer of 4 bursts */ + burst = *bursts_p + bid * 116; + memcpy(burst, bits + 3, 58); + memcpy(burst + 58, bits + 87, 58); + + /* wait until complete set of bursts */ + if (bid != 3) + return 0; + + /* check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGP(DL1C, LOGL_NOTICE, "Received incomplete PDTCH block " + "ending at fn=%u (%u/%u) for %s\n", fn, + fn % l1ts->mf_period, l1ts->mf_period, + trx_chan_desc[chan].name); + } + *mask = 0x0; + + /* decode */ + rc = pdtch_decode(l2 + 1, *bursts_p, NULL, &n_errors, &n_bits_total); + + /* Send uplnk measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, + n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa_sum / *toa_num); + + if (rc <= 0) { + LOGP(DL1C, LOGL_NOTICE, "Received bad PDTCH block ending at " + "fn=%u (%u/%u) for %s\n", fn, fn % l1ts->mf_period, + l1ts->mf_period, trx_chan_desc[chan].name); + return 0; + } + + l2[0] = 7; /* valid frame */ + + return _sched_compose_ph_data_ind(l1t, tn, (fn + GSM_HYPERFRAME - 3) % GSM_HYPERFRAME, chan, + l2, rc + 1, *rssi_sum / *rssi_num); +} + +int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, + float toa) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, 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, n_bits_total; + + /* handle rach, if handover rach detection is turned on */ + if (chan_state->ho_rach_detect == 1) + return rx_rach_fn(l1t, tn, fn, chan, bid, bits, rssi, toa); + + LOGP(DL1C, LOGL_DEBUG, "TCH/F received %s fn=%u ts=%u trx=%u bid=%u\n", + trx_chan_desc[chan].name, fn, tn, l1t->trx->nr, bid); + + /* alloc 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; + memcpy(burst, bits + 3, 58); + memcpy(burst + 58, bits + 87, 58); + + /* wait until complete set of bursts */ + if (bid != 3) + return 0; + + /* check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGP(DL1C, LOGL_NOTICE, "Received incomplete TCH frame ending " + "at fn=%u (%u/%u) for %s\n", fn, + fn % l1ts->mf_period, l1ts->mf_period, + trx_chan_desc[chan].name); + } + *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 = tch_fr_decode(tch_data, *bursts_p, 1, 0, &n_errors, &n_bits_total); + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + rc = 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. + */ + rc = tch_afs_decode(tch_data + 2, *bursts_p, + (((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); + if (rc) + trx_loop_amr_input(l1t, + trx_chan_desc[chan].chan_nr | tn, chan_state, + (float)n_errors/(float)n_bits_total); + amr = 2; /* we store tch_data + 2 header bytes */ + /* only good speech frames get rtp header */ + if (rc != GSM_MACBLOCK_LEN && rc >= 4) { + rc = amr_compose_payload(tch_data, + chan_state->codec[chan_state->ul_cmr], + chan_state->codec[chan_state->ul_ft], 0); + } + break; + default: + LOGP(DL1C, LOGL_ERROR, "TCH mode %u invalid, please fix!\n", + tch_mode); + return -EINVAL; + } + memcpy(*bursts_p, *bursts_p + 464, 464); + + /* Send uplnk measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, fn, trx_chan_desc[chan].chan_nr|tn, + n_errors, n_bits_total, rssi, toa); + + /* Check if the frame is bad */ + if (rc < 0) { + LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " + "fn=%u for %s\n", fn, trx_chan_desc[chan].name); + goto bfi; + } + if (rc < 4) { + LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " + "fn=%u for %s with codec mode %d (out of range)\n", + fn, trx_chan_desc[chan].name, rc); + goto bfi; + } + + /* FACCH */ + if (rc == GSM_MACBLOCK_LEN) { + _sched_compose_ph_data_ind(l1t, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan, + tch_data + amr, GSM_MACBLOCK_LEN, rssi); +bfi: + if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { + /* indicate bad frame */ + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR */ + memset(tch_data, 0, GSM_FR_BYTES); + rc = GSM_FR_BYTES; + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + memset(tch_data, 0, GSM_EFR_BYTES); + rc = GSM_EFR_BYTES; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + rc = amr_compose_payload(tch_data, + chan_state->codec[chan_state->dl_cmr], + chan_state->codec[chan_state->dl_ft], + 1); + if (rc < 2) + break; + memset(tch_data + 2, 0, rc - 2); + break; + default: + LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, " + "please fix!\n"); + return -EINVAL; + } + } + } + + if (rsl_cmode != RSL_CMOD_SPD_SPEECH) + return 0; + + /* TCH or BFI */ + return _sched_compose_tch_ind(l1t, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan, + tch_data, rc); +} + +int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int8_t rssi, + float toa) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, 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, n_bits_total; + + /* handle rach, if handover rach detection is turned on */ + if (chan_state->ho_rach_detect == 1) + return rx_rach_fn(l1t, tn, fn, chan, bid, bits, rssi, toa); + + LOGP(DL1C, LOGL_DEBUG, "TCH/H received %s fn=%u ts=%u trx=%u bid=%u\n", + trx_chan_desc[chan].name, fn, tn, l1t->trx->nr, bid); + + /* alloc 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; + memcpy(burst, bits + 3, 58); + memcpy(burst + 58, bits + 87, 58); + + /* wait until complete set of bursts */ + if (bid != 1) + return 0; + + /* check for complete set of bursts */ + if ((*mask & 0x3) != 0x3) { + LOGP(DL1C, LOGL_NOTICE, "Received incomplete TCH frame ending " + "at fn=%u (%u/%u) for %s\n", fn, + fn % l1ts->mf_period, l1ts->mf_period, + trx_chan_desc[chan].name); + } + *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); + 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 = tch_hr_decode(tch_data, *bursts_p, + (((fn + 26 - 10) % 26) >> 2) & 1, + &n_errors, &n_bits_total); + 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. + */ + rc = tch_ahs_decode(tch_data + 2, *bursts_p, + (((fn + 26 - 10) % 26) >> 2) & 1, + (((fn + 26 - 10) % 26) >> 2) & 1, chan_state->codec, + chan_state->codecs, &chan_state->ul_ft, + &chan_state->ul_cmr, &n_errors, &n_bits_total); + if (rc) + trx_loop_amr_input(l1t, + trx_chan_desc[chan].chan_nr | tn, chan_state, + (float)n_errors/(float)n_bits_total); + amr = 2; /* we store tch_data + 2 two */ + /* only good speech frames get rtp header */ + if (rc != GSM_MACBLOCK_LEN && rc >= 4) { + rc = amr_compose_payload(tch_data, + chan_state->codec[chan_state->ul_cmr], + chan_state->codec[chan_state->ul_ft], 0); + } + break; + default: + LOGP(DL1C, LOGL_ERROR, "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); + + /* Send uplnk measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, fn, trx_chan_desc[chan].chan_nr|tn, + n_errors, n_bits_total, rssi, toa); + + /* Check if the frame is bad */ + if (rc < 0) { + LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " + "fn=%u for %s\n", fn, trx_chan_desc[chan].name); + goto bfi; + } + if (rc < 4) { + LOGP(DL1C, LOGL_NOTICE, "Received bad TCH frame ending at " + "fn=%u for %s with codec mode %d (out of range)\n", + fn, trx_chan_desc[chan].name, rc); + goto bfi; + } + + /* FACCH */ + if (rc == GSM_MACBLOCK_LEN) { + chan_state->ul_ongoing_facch = 1; + _sched_compose_ph_data_ind(l1t, tn, + (fn + GSM_HYPERFRAME - 10 - ((fn % 26) >= 19)) % GSM_HYPERFRAME, chan, + tch_data + amr, GSM_MACBLOCK_LEN, rssi); +bfi: + if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { + /* indicate bad frame */ + 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 = amr_compose_payload(tch_data, + chan_state->codec[chan_state->dl_cmr], + chan_state->codec[chan_state->dl_ft], + 1); + if (rc < 2) + break; + memset(tch_data + 2, 0, rc - 2); + break; + default: + LOGP(DL1C, LOGL_ERROR, "TCH mode invalid, " + "please fix!\n"); + return -EINVAL; + } + } + } + + if (rsl_cmode != RSL_CMOD_SPD_SPEECH) + return 0; + + /* 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 substracted to get correct + * start of frame. + */ + return _sched_compose_tch_ind(l1t, tn, + (fn + GSM_HYPERFRAME - 10 - ((fn%26)==19) - ((fn%26)==20)) % GSM_HYPERFRAME, + chan, tch_data, rc); +} + +/* schedule all frames of all TRX for given FN */ +static int trx_sched_fn(struct gsm_bts *bts, uint32_t fn) +{ + struct gsm_bts_trx *trx; + uint8_t tn; + const ubit_t *bits; + uint8_t gain; + + /* send time indication */ + l1if_mph_time_ind(bts, fn); + + /* advance frame number, so the transceiver has more time until + * it must be transmitted. */ + fn = (fn + trx_clock_advance) % GSM_HYPERFRAME; + + /* process every TRX */ + llist_for_each_entry(trx, &bts->trx_list, list) { + struct trx_l1h *l1h = trx_l1h_hdl(trx); + struct l1sched_trx *l1t = trx_l1sched_hdl(trx); + + /* we don't schedule, if power is off */ + if (!trx_if_powered(l1h)) + continue; + + /* process every TS of TRX */ + for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { + /* ready-to-send */ + _sched_rts(l1t, tn, + (fn + trx_rts_advance) % GSM_HYPERFRAME); + /* get burst for FN */ + bits = _sched_dl_burst(l1t, tn, fn); + if (!bits) { + /* if no bits, send no burst */ + continue; + } else + gain = 0; + trx_if_data(l1h, tn, fn, gain, bits); + } + } + + return 0; +} + + +/* + * frame clock + */ + +#define FRAME_DURATION_uS 4615 +#define MAX_FN_SKEW 50 +#define TRX_LOSS_FRAMES 400 + +extern int quit; +/* this timer fires for every FN to be processed */ +static void trx_ctrl_timer_cb(void *data) +{ + struct gsm_bts *bts = data; + struct timeval tv_now, *tv_clock = &transceiver_clock_tv; + int32_t elapsed; + + /* check if transceiver is still alive */ + if (transceiver_lost++ == TRX_LOSS_FRAMES) { + struct gsm_bts_trx *trx; + + LOGP(DL1C, LOGL_NOTICE, "No more clock from transceiver\n"); + +no_clock: + transceiver_available = 0; + + /* flush pending messages of transceiver */ + /* close all logical channels and reset timeslots */ + llist_for_each_entry(trx, &bts->trx_list, list) { + trx_if_flush(trx_l1h_hdl(trx)); + trx_sched_reset(trx_l1sched_hdl(trx)); + if (trx->nr == 0) + trx_if_cmd_poweroff(trx_l1h_hdl(trx)); + } + + /* tell BSC */ + check_transceiver_availability(bts, 0); + + return; + } + + gettimeofday(&tv_now, NULL); + + elapsed = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000 + + (tv_now.tv_usec - tv_clock->tv_usec); + + /* if someone played with clock, or if the process stalled */ + if (elapsed > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed < 0) { + LOGP(DL1C, LOGL_NOTICE, "PC clock skew: elapsed uS %d\n", + elapsed); + goto no_clock; + } + + /* schedule next FN clock */ + while (elapsed > FRAME_DURATION_uS / 2) { + tv_clock->tv_usec += FRAME_DURATION_uS; + if (tv_clock->tv_usec >= 1000000) { + tv_clock->tv_sec++; + tv_clock->tv_usec -= 1000000; + } + transceiver_last_fn = (transceiver_last_fn + 1) % GSM_HYPERFRAME; + trx_sched_fn(bts, transceiver_last_fn); + elapsed -= FRAME_DURATION_uS; + } + osmo_timer_schedule(&transceiver_clock_timer, 0, + FRAME_DURATION_uS - elapsed); +} + + +/* receive clock from transceiver */ +int trx_sched_clock(struct gsm_bts *bts, uint32_t fn) +{ + struct timeval tv_now, *tv_clock = &transceiver_clock_tv; + int32_t elapsed; + int32_t elapsed_fn; + + if (quit) + return 0; + + /* reset lost counter */ + transceiver_lost = 0; + + gettimeofday(&tv_now, NULL); + + /* clock becomes valid */ + if (!transceiver_available) { + LOGP(DL1C, LOGL_NOTICE, "initial GSM clock received: fn=%u\n", + fn); + + transceiver_available = 1; + + /* start provisioning transceiver */ + l1if_provision_transceiver(bts); + + /* tell BSC */ + check_transceiver_availability(bts, 1); + +new_clock: + transceiver_last_fn = fn; + trx_sched_fn(bts, transceiver_last_fn); + + /* schedule first FN clock */ + memcpy(tv_clock, &tv_now, sizeof(struct timeval)); + memset(&transceiver_clock_timer, 0, + sizeof(transceiver_clock_timer)); + transceiver_clock_timer.cb = trx_ctrl_timer_cb; + transceiver_clock_timer.data = bts; + osmo_timer_schedule(&transceiver_clock_timer, 0, + FRAME_DURATION_uS); + + return 0; + } + + osmo_timer_del(&transceiver_clock_timer); + + /* calculate elapsed time since last_fn */ + elapsed = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000 + + (tv_now.tv_usec - tv_clock->tv_usec); + + /* how much frames have been elapsed since last fn processed */ + elapsed_fn = (fn + GSM_HYPERFRAME - transceiver_last_fn) % GSM_HYPERFRAME; + if (elapsed_fn >= 135774) + elapsed_fn -= GSM_HYPERFRAME; + + /* check for max clock skew */ + if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) { + LOGP(DL1C, LOGL_NOTICE, "GSM clock skew: old fn=%u, " + "new fn=%u\n", transceiver_last_fn, fn); + goto new_clock; + } + + LOGP(DL1C, LOGL_INFO, "GSM clock jitter: %d\n", + elapsed_fn * FRAME_DURATION_uS - elapsed); + + /* too many frames have been processed already */ + if (elapsed_fn < 0) { + /* set clock to the time or last FN should have been + * transmitted. */ + tv_clock->tv_sec = tv_now.tv_sec; + tv_clock->tv_usec = tv_now.tv_usec + + (0 - elapsed_fn) * FRAME_DURATION_uS; + if (tv_clock->tv_usec >= 1000000) { + tv_clock->tv_sec++; + tv_clock->tv_usec -= 1000000; + } + /* set time to the time our next FN has to be transmitted */ + osmo_timer_schedule(&transceiver_clock_timer, 0, + FRAME_DURATION_uS * (1 - elapsed_fn)); + + return 0; + } + + /* transmit what we still need to transmit */ + while (fn != transceiver_last_fn) { + transceiver_last_fn = (transceiver_last_fn + 1) % GSM_HYPERFRAME; + trx_sched_fn(bts, transceiver_last_fn); + } + + /* schedule next FN to be transmitted */ + memcpy(tv_clock, &tv_now, sizeof(struct timeval)); + osmo_timer_schedule(&transceiver_clock_timer, 0, FRAME_DURATION_uS); + + return 0; +} + +void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate) +{ + struct trx_l1h *l1h = trx_l1h_hdl(l1t->trx); + + if (activate) + trx_if_cmd_handover(l1h, tn, ss); + else + trx_if_cmd_nohandover(l1h, tn, ss); +} diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c index 419fea64..dbe6f68c 100644 --- a/src/osmo-bts-trx/trx_if.c +++ b/src/osmo-bts-trx/trx_if.c @@ -36,10 +36,10 @@ #include <osmo-bts/logging.h> #include <osmo-bts/bts.h> +#include <osmo-bts/scheduler.h> #include "l1_if.h" #include "trx_if.h" -#include "scheduler.h" /* enable to print RSSI level graph */ //#define TOA_RSSI_DEBUG @@ -121,6 +121,7 @@ static struct osmo_fd trx_ofd_clk; /* get clock from clock socket */ static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what) { + struct trx_l1h *l1h = ofd->data; char buf[1500]; int len; uint32_t fn; @@ -145,7 +146,7 @@ static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what) "correctly, correcting to fn=%u\n", fn); } - trx_sched_clock(fn); + trx_sched_clock(l1h->trx->bts, fn); return 0; } @@ -454,7 +455,7 @@ static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what) fprintf(stderr, "%s\n", deb); #endif - trx_sched_ul_burst(l1h, tn, fn, bits, rssi, toa); + trx_sched_ul_burst(&l1h->l1s, tn, fn, bits, rssi, toa); return 0; } @@ -503,7 +504,7 @@ int trx_if_open(struct trx_l1h *l1h) /* open sockets */ if (l1h->trx->nr == 0) { - rc = trx_udp_open(NULL, &trx_ofd_clk, base_port_local, + rc = trx_udp_open(l1h, &trx_ofd_clk, base_port_local, trx_clk_read_cb); if (rc < 0) return rc; @@ -558,3 +559,7 @@ void trx_if_close(struct trx_l1h *l1h) trx_udp_close(&l1h->trx_ofd_data); } +int trx_if_powered(struct trx_l1h *l1h) +{ + return l1h->config.poweron; +} diff --git a/src/osmo-bts-trx/trx_if.h b/src/osmo-bts-trx/trx_if.h index ac0ee42c..3862e2be 100644 --- a/src/osmo-bts-trx/trx_if.h +++ b/src/osmo-bts-trx/trx_if.h @@ -31,5 +31,6 @@ int trx_if_data(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr, int trx_if_open(struct trx_l1h *l1h); void trx_if_flush(struct trx_l1h *l1h); void trx_if_close(struct trx_l1h *l1h); +int trx_if_powered(struct trx_l1h *l1h); #endif /* TRX_IF_H */ diff --git a/src/osmo-bts-trx/trx_vty.c b/src/osmo-bts-trx/trx_vty.c index 5da0c21f..a4a79091 100644 --- a/src/osmo-bts-trx/trx_vty.c +++ b/src/osmo-bts-trx/trx_vty.c @@ -39,9 +39,9 @@ #include <osmo-bts/gsm_data.h> #include <osmo-bts/logging.h> #include <osmo-bts/vty.h> +#include <osmo-bts/scheduler.h> #include "l1_if.h" -#include "scheduler.h" #include "trx_if.h" #include "loops.h" |