diff options
Diffstat (limited to 'src')
96 files changed, 6515 insertions, 4131 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 25fdab2a..8b8f31ed 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,7 +19,7 @@ # AUTOMAKE_OPTIONS = subdir-objects -AM_CPPFLAGS = -I$(top_srcdir)/include $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGB_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOGSM_CFLAGS) +AM_CPPFLAGS = -I$(top_srcdir)/include $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGB_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOTRAU_CFLAGS) if ENABLE_SYSMODSP AM_CPPFLAGS += -DENABLE_DIRECT_PHY @@ -33,25 +33,27 @@ if ENABLE_OC2GBTS_PHY AM_CPPFLAGS += -DENABLE_DIRECT_PHY endif -AM_CXXFLAGS = -Wall -ldl -pthread -AM_LDFLAGS = -lrt +if ENABLE_ER_E1_CCU +AM_CPPFLAGS += -DENABLE_DIRECT_PHY +endif + +AM_CXXFLAGS = -Wall noinst_LTLIBRARIES = libgprs.la libgprs_la_SOURCES = \ - gprs_debug.cpp \ + alloc_algo.cpp \ + gprs_debug.c \ csn1.c \ csn1_dec.c \ csn1_enc.c \ gsm_rlcmac.c \ gprs_bssgp_pcu.c \ gprs_bssgp_rim.c \ - gprs_rlcmac.cpp \ + gprs_rlcmac.c \ gprs_rlcmac_sched.cpp \ gprs_rlcmac_meas.cpp \ - gprs_rlcmac_ts_alloc.cpp \ gprs_ms.c \ - gprs_ms_storage.cpp \ gprs_pcu.c \ pcu_l1_if.cpp \ pcu_vty.c \ @@ -62,9 +64,11 @@ libgprs_la_SOURCES = \ tbf.cpp \ tbf_fsm.c \ tbf_ul.cpp \ + tbf_ul_fsm.c \ tbf_ul_ack_fsm.c \ tbf_ul_ass_fsm.c \ tbf_dl.cpp \ + tbf_dl_fsm.c \ tbf_dl_ass_fsm.c \ bts.cpp \ bts_pch_timer.c \ @@ -73,9 +77,12 @@ libgprs_la_SOURCES = \ encoding.cpp \ sba.c \ decoding.cpp \ - llc.cpp \ + llc.c \ rlc.cpp \ - osmobts_sock.c \ + rlc_window.cpp \ + rlc_window_dl.cpp \ + rlc_window_ul.cpp \ + pcuif_sock.c \ gprs_codel.c \ coding_scheme.c \ egprs_rlc_compression.cpp \ @@ -87,6 +94,7 @@ bin_PROGRAMS = \ noinst_PROGRAMS = noinst_HEADERS = \ + alloc_algo.h \ gprs_debug.h \ csn1.h \ gsm_rlcmac.h \ @@ -94,9 +102,9 @@ noinst_HEADERS = \ gprs_bssgp_rim.h \ gprs_rlcmac.h \ gprs_ms.h \ - gprs_ms_storage.h \ gprs_pcu.h \ pcu_l1_if.h \ + pcu_l1_if_phy.h \ pcu_vty.h \ pcu_vty_functions.h \ mslot_class.h \ @@ -116,6 +124,9 @@ noinst_HEADERS = \ encoding.h \ sba.h \ rlc.h \ + rlc_window.h \ + rlc_window_dl.h \ + rlc_window_ul.h \ decoding.h \ llc.h \ pcu_utils.h \ @@ -130,13 +141,6 @@ osmo_pcu_SOURCES = pcu_main.cpp if ENABLE_SYSMODSP AM_CPPFLAGS += -I$(srcdir)/osmo-bts-sysmo -I$(SYSMOBTS_INCDIR) -EXTRA_DIST = \ - osmo-bts-sysmo/sysmo_l1_if.c \ - osmo-bts-sysmo/sysmo_l1_if.h \ - osmo-bts-sysmo/sysmo_l1_hw.c \ - osmo-bts-sysmo/femtobts.c \ - osmo-bts-sysmo/femtobts.h - noinst_HEADERS += \ osmo-bts-sysmo/sysmo_l1_if.h \ osmo-bts-sysmo/femtobts.h @@ -161,19 +165,13 @@ osmo_pcu_remote_LDADD = \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOGSM_LIBS) \ - $(COMMON_LA) + -lrt \ + $(NULL) endif if ENABLE_LC15BTS_PHY AM_CPPFLAGS += $(LITECELL15_CFLAGS) -I$(srcdir)/osmo-bts-litecell15 -EXTRA_DIST = \ - osmo-bts-litecell15/lc15_l1_if.c \ - osmo-bts-litecell15/lc15_l1_if.h \ - osmo-bts-litecell15/lc15_l1_hw.c \ - osmo-bts-litecell15/lc15bts.c \ - osmo-bts-litecell15/lc15bts.h - noinst_HEADERS += \ osmo-bts-litecell15/lc15_l1_if.h \ osmo-bts-litecell15/lc15bts.h @@ -187,13 +185,6 @@ endif if ENABLE_OC2GBTS_PHY AM_CPPFLAGS += -I$(OC2G_INCDIR) -I$(srcdir)/osmo-bts-oc2g -EXTRA_DIST = \ - osmo-bts-oc2g/oc2g_l1_if.c \ - osmo-bts-oc2g/oc2g_l1_if.h \ - osmo-bts-oc2g/oc2g_l1_hw.c \ - osmo-bts-oc2g/oc2gbts.c \ - osmo-bts-oc2g/oc2gbts.h - noinst_HEADERS += \ osmo-bts-oc2g/oc2g_l1_if.h \ osmo-bts-oc2g/oc2gbts.h @@ -204,12 +195,27 @@ osmo_pcu_SOURCES += \ osmo-bts-oc2g/oc2gbts.c endif +if ENABLE_ER_E1_CCU +AM_CPPFLAGS += -I$(srcdir)/ericsson-rbs + +noinst_HEADERS += \ + ericsson-rbs/er_ccu_if.h \ + ericsson-rbs/er_ccu_descr.h + +osmo_pcu_SOURCES += \ + ericsson-rbs/er_ccu_l1_if.c \ + ericsson-rbs/er_ccu_if.c +endif + osmo_pcu_LDADD = \ libgprs.la \ $(LIBOSMOGB_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOGSM_LIBS) \ - $(COMMON_LA) + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOTRAU_LIBS) \ + -lrt \ + $(NULL) #MOSTLYCLEANFILES += testSource testDestination diff --git a/src/gprs_rlcmac_ts_alloc.cpp b/src/alloc_algo.cpp index 5b3b355a..11789c34 100644 --- a/src/gprs_rlcmac_ts_alloc.cpp +++ b/src/alloc_algo.cpp @@ -1,8 +1,9 @@ -/* gprs_rlcmac.cpp +/* alloc_algo.cpp * * Copyright (C) 2012 Ivan Klyuchnikov * Copyright (C) 2012 Andreas Eversberg <jolly@eversberg.eu> * Copyright (C) 2013 by Holger Hans Peter Freyther + * Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -13,10 +14,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <gprs_rlcmac.h> @@ -33,6 +30,7 @@ extern "C" { #include "mslot_class.h" +#include "alloc_algo.h" #include <osmocom/core/linuxlist.h> #include <osmocom/core/logging.h> #include <osmocom/core/utils.h> @@ -41,12 +39,12 @@ extern "C" { /* Consider a PDCH as idle if has at most this number of TBFs assigned to it */ #define PDCH_IDLE_TBF_THRESH 1 -#define LOGPSL(tbf, level, fmt, args...) LOGP(DRLCMAC, level, "[%s] " fmt, \ - (tbf->direction == GPRS_RLCMAC_DL_TBF) ? "DL" : "UL", ## args) +#define LOGPSL(req, level, fmt, args...) LOGP(DRLCMAC, level, "[%s] " fmt, \ + (req->direction == GPRS_RLCMAC_DL_TBF) ? "DL" : "UL", ## args) -#define LOGPAL(tbf, kind, single, trx_n, level, fmt, args...) LOGPSL(tbf, level, \ +#define LOGPAL(req, kind, level, fmt, args...) LOGPSL(req, level, \ "algo %s <%s> (suggested TRX: %d): " fmt, \ - kind, single ? "single" : "multi", trx_n, ## args) + kind, req->single ? "single" : "multi", req->use_trx, ## args) static char *set_flag_chars(char *buf, uint8_t val, char set_char, char unset_char = 0) { @@ -216,44 +214,21 @@ static int find_least_busy_pdch(const struct gprs_rlcmac_trx *trx, enum gprs_rlc return min_ts; } -static void attach_tbf_to_pdch(struct gprs_rlcmac_pdch *pdch, - struct gprs_rlcmac_tbf *tbf) -{ - if (tbf->pdch[pdch->ts_no]) - tbf->pdch[pdch->ts_no]->detach_tbf(tbf); - - tbf->pdch[pdch->ts_no] = pdch; - pdch->attach_tbf(tbf); -} - -static void assign_uplink_tbf_usf(struct gprs_rlcmac_pdch *pdch, struct gprs_rlcmac_ul_tbf *tbf, uint8_t tfi, int8_t usf) -{ - tbf->m_tfi = tfi; - tbf->m_usf[pdch->ts_no] = usf; - attach_tbf_to_pdch(pdch, tbf); -} - -static void assign_dlink_tbf(struct gprs_rlcmac_pdch *pdch, struct gprs_rlcmac_dl_tbf *tbf, uint8_t tfi) -{ - tbf->m_tfi = tfi; - attach_tbf_to_pdch(pdch, tbf); -} - -static int find_trx(const struct gprs_rlcmac_bts *bts, const GprsMs *ms, int8_t use_trx) +static int find_trx(const struct alloc_resources_req *req) { unsigned trx_no; unsigned ts; /* We must use the TRX currently actively used by an MS */ - if (ms && ms_current_trx(ms)) - return ms_current_trx(ms)->trx_no; + if (req->ms && ms_current_trx(req->ms)) + return ms_current_trx(req->ms)->trx_no; - if (use_trx >= 0 && use_trx < 8) - return use_trx; + if (req->use_trx >= 0 && req->use_trx < 8) + return req->use_trx; /* Find the first TRX that has a PDCH with a free UL and DL TFI */ - for (trx_no = 0; trx_no < ARRAY_SIZE(bts->trx); trx_no += 1) { - const struct gprs_rlcmac_trx *trx = &bts->trx[trx_no]; + for (trx_no = 0; trx_no < ARRAY_SIZE(req->bts->trx); trx_no += 1) { + const struct gprs_rlcmac_trx *trx = &req->bts->trx[trx_no]; for (ts = 0; ts < ARRAY_SIZE(trx->pdch); ts++) { const struct gprs_rlcmac_pdch *pdch = &trx->pdch[ts]; if (!pdch->is_enabled()) @@ -297,33 +272,28 @@ static bool idle_pdch_avail(const struct gprs_rlcmac_bts *bts) /*! Return free TFI * - * \param[in] bts Pointer to BTS struct - * \param[in] trx Optional pointer to TRX struct - * \param[in] ms Pointer to MS object - * \param[in] dir DL or UL direction - * \param[in] use_trx which TRX to use or -1 if it should be selected based on what MS uses + * \param[in] req Contains all the requested params * \param[out] trx_no_ TRX number on which TFI was found * \returns negative error code or 0 on success */ -static int tfi_find_free(const struct gprs_rlcmac_bts *bts, const gprs_rlcmac_trx *trx, const GprsMs *ms, - enum gprs_rlcmac_tbf_direction dir, int8_t use_trx, uint8_t *trx_no_) +static int tfi_find_free(const struct alloc_resources_req *req, uint8_t *trx_no_) { + const struct gprs_rlcmac_trx *trx; int tfi; + int8_t use_trx = req->use_trx; uint8_t trx_no; - if (trx) { + /* If MS is already doing stuff on a TRX, set use_trx to it: */ + if ((trx = ms_current_trx(req->ms))) { if (use_trx >= 0 && use_trx != trx->trx_no) { LOGP(DRLCMAC, LOGL_ERROR, "- Requested incompatible TRX %d (current is %d)\n", - use_trx, trx->trx_no); + req->use_trx, trx->trx_no); return -EINVAL; } use_trx = trx->trx_no; } - if (use_trx == -1 && ms_current_trx(ms)) - use_trx = ms_current_trx(ms)->trx_no; - - tfi = bts_tfi_find_free(bts, dir, &trx_no, use_trx); + tfi = bts_tfi_find_free(req->bts, req->direction, &trx_no, use_trx); if (tfi < 0) return -EBUSY; @@ -337,16 +307,13 @@ static int tfi_find_free(const struct gprs_rlcmac_bts *bts, const gprs_rlcmac_tr * * Assign single slot for uplink and downlink * - * \param[in,out] bts Pointer to BTS struct - * \param[in,out] tbf Pointer to TBF struct - * \param[in] single flag indicating if we should force single-slot allocation - * \param[in] use_trx which TRX to use or -1 if it should be selected during allocation + * \param[in] req Contains all the requested params + * \param[out] res The resolution/response for the allocation request * \returns negative error code or 0 on success */ -int alloc_algorithm_a(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, bool single, - int8_t use_trx) +int alloc_algorithm_a(const struct alloc_resources_req *req, + struct alloc_resources_res *res) { - struct gprs_rlcmac_pdch *pdch; int ts = -1; uint8_t ul_slots, dl_slots; int trx_no; @@ -354,28 +321,26 @@ int alloc_algorithm_a(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, int usf = -1; uint8_t mask = 0xff; const char *mask_reason = NULL; - struct GprsMs *ms = tbf->ms(); - gprs_rlcmac_trx *trx = ms_current_trx(ms); + gprs_rlcmac_trx *trx = ms_current_trx(req->ms); + struct gprs_rlcmac_pdch *first_common_ts = ms_first_common_ts(req->ms); - LOGPAL(tbf, "A", single, use_trx, LOGL_DEBUG, "Alloc start\n"); + LOGPAL(req, "A", LOGL_DEBUG, "Alloc start\n"); - trx_no = find_trx(bts, ms, use_trx); + trx_no = find_trx(req); if (trx_no < 0) { - LOGPAL(tbf, "A", single, use_trx, LOGL_NOTICE, + LOGPAL(req, "A", LOGL_NOTICE, "failed to find a usable TRX (TFI exhausted)\n"); return trx_no; } if (!trx) - trx = &bts->trx[trx_no]; - - dl_slots = ms_reserved_dl_slots(ms); - ul_slots = ms_reserved_ul_slots(ms); + trx = &req->bts->trx[trx_no]; - ts = ms_first_common_ts(ms); + dl_slots = ms_reserved_dl_slots(req->ms); + ul_slots = ms_reserved_ul_slots(req->ms); - if (ts >= 0) { + if (first_common_ts) { mask_reason = "need to reuse TS"; - mask = 1 << ts; + mask = 1 << first_common_ts->ts_no; } else if (dl_slots || ul_slots) { mask_reason = "need to use a reserved common TS"; mask = dl_slots & ul_slots; @@ -385,43 +350,32 @@ int alloc_algorithm_a(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, if (!mask) return -EINVAL; - ts = find_least_busy_pdch(trx, tbf->direction, mask, + ts = find_least_busy_pdch(trx, req->direction, mask, compute_usage_for_algo_a, &tfi, &usf); - if (tbf->direction == GPRS_RLCMAC_UL_TBF && usf < 0) { - LOGPAL(tbf, "A", single, use_trx, LOGL_NOTICE, + if (req->direction == GPRS_RLCMAC_UL_TBF && usf < 0) { + LOGPAL(req, "A", LOGL_NOTICE, "failed to allocate a TS, no USF available\n"); return -EBUSY; } if (ts < 0) { - LOGPAL(tbf, "A", single, use_trx, LOGL_NOTICE, + LOGPAL(req, "A", LOGL_NOTICE, "failed to allocate a TS, no TFI available\n"); return -EBUSY; } - pdch = &trx->pdch[ts]; + res->trx = trx; + res->first_common_ts = &trx->pdch[ts]; + res->reserved_ul_slots = 1 << ts; + res->reserved_dl_slots = 1 << ts; + res->ass_slots_mask = 1 << ts; + res->upgrade_to_multislot = false; + res->tfi = tfi; + res->usf[ts] = usf; - /* The allocation will be successful, so the system state and tbf/ms - * may be modified from now on. */ - if (tbf->direction == GPRS_RLCMAC_UL_TBF) { - struct gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(tbf); - LOGPSL(tbf, LOGL_DEBUG, "Assign uplink TS=%d TFI=%d USF=%d\n", ts, tfi, usf); - assign_uplink_tbf_usf(pdch, ul_tbf, tfi, usf); - } else { - struct gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(tbf); - LOGPSL(tbf, LOGL_DEBUG, "Assign downlink TS=%d TFI=%d\n", ts, tfi); - assign_dlink_tbf(pdch, dl_tbf, tfi); - } - - tbf->trx = trx; - /* the only one TS is the common TS */ - tbf->first_ts = tbf->first_common_ts = ts; - ms_set_reserved_slots(ms, trx, 1 << ts, 1 << ts); - - tbf->upgrade_to_multislot = false; - bts_do_rate_ctr_inc(bts, CTR_TBF_ALLOC_ALGO_A); + bts_do_rate_ctr_inc(req->bts, CTR_TBF_ALLOC_ALGO_A); return 0; } @@ -661,31 +615,31 @@ int find_multi_slots(struct gprs_rlcmac_trx *trx, uint8_t mslot_class, uint8_t * * \param[in] slots Timeslots in use * \param[in] reserved_slots Reserved timeslots * \param[out] slotcount Number of TS in use - * \param[out] avail_count Number of reserved TS + * \param[out] reserve_count Number of reserved TS */ -static void update_slot_counters(uint8_t slots, uint8_t reserved_slots, uint8_t *slotcount, uint8_t *avail_count) +static void count_slots(uint8_t slots, uint8_t reserved_slots, uint8_t *slotcount, uint8_t *reserve_count) { (*slotcount) = pcu_bitcount(slots); - (*avail_count) = pcu_bitcount(reserved_slots); + (*reserve_count) = pcu_bitcount(reserved_slots); } /*! Return slot mask with single TS from a given UL/DL set according to TBF's direction, ts pointer is set to that TS * number or to negative value on error * * \param[in] trx Pointer to TRX object - * \param[in] tbf Pointer to TBF object + * \param[in] direction Direction of the TBF to allocate * \param[in] dl_slots set of DL timeslots * \param[in] ul_slots set of UL timeslots * \param[in] ts corresponding TS or -1 for autoselection * \returns slot mask with single UL or DL timeslot number if possible */ -static uint8_t get_single_ts(const gprs_rlcmac_trx *trx, const gprs_rlcmac_tbf *tbf, uint8_t dl_slots, uint8_t ul_slots, +static uint8_t get_single_ts(const gprs_rlcmac_trx *trx, enum gprs_rlcmac_tbf_direction direction, uint8_t dl_slots, uint8_t ul_slots, int ts) { uint8_t ret = dl_slots & ul_slots; /* Make sure to consider the first common slot only */ if (ts < 0) - ts = find_least_busy_pdch(trx, tbf->direction, ret, compute_usage_by_num_tbfs, NULL, NULL); + ts = find_least_busy_pdch(trx, direction, ret, compute_usage_by_num_tbfs, NULL, NULL); if (ts < 0) return ffs(ret); @@ -695,9 +649,8 @@ static uint8_t get_single_ts(const gprs_rlcmac_trx *trx, const gprs_rlcmac_tbf * /*! Find set of timeslots available for allocation * + * \param[in] req Contains all the requested params * \param[in] trx Pointer to TRX object - * \param[in] tbf Pointer to TBF object - * \param[in] single Flag to force the single TS allocation * \param[in] ul_slots set of UL timeslots * \param[in] dl_slots set of DL timeslots * \param[in] reserved_ul_slots set of reserved UL timeslots @@ -705,17 +658,17 @@ static uint8_t get_single_ts(const gprs_rlcmac_trx *trx, const gprs_rlcmac_tbf * * \param[in] first_common_ts First TS common for both UL and DL or -1 if unknown * \returns negative error code or selected TS on success */ -static int tbf_select_slot_set(const gprs_rlcmac_tbf *tbf, const gprs_rlcmac_trx *trx, bool single, +static int tbf_select_slot_set(const struct alloc_resources_req *req, const gprs_rlcmac_trx *trx, uint8_t ul_slots, uint8_t dl_slots, uint8_t reserved_ul_slots, uint8_t reserved_dl_slots, int8_t first_common_ts) { - bool is_ul = tbf->direction == GPRS_RLCMAC_UL_TBF; + bool is_ul = req->direction == GPRS_RLCMAC_UL_TBF; uint8_t sl = is_ul ? ul_slots : dl_slots; char slot_info[9] = { 0 }; - if (single) - sl = get_single_ts(trx, tbf, dl_slots, ul_slots, first_common_ts); + if (req->single) + sl = get_single_ts(trx, req->direction, dl_slots, ul_slots, first_common_ts); if (!sl) { LOGP(DRLCMAC, LOGL_NOTICE, "No %s slots available\n", @@ -734,7 +687,7 @@ static int tbf_select_slot_set(const gprs_rlcmac_tbf *tbf, const gprs_rlcmac_trx LOGPC(DRLCMAC, LOGL_DEBUG, "Selected %s slots: (TS=0)\"%s\"(TS=7), %s\n", is_ul ? "UL" : "DL", - slot_info, single ? "single" : "multi"); + slot_info, req->single ? "single" : "multi"); return sl; } @@ -780,125 +733,56 @@ static int allocate_usf(const gprs_rlcmac_trx *trx, uint8_t selected_ul_slots, u return ul_slots; } -/*! Update MS' reserved timeslots - * - * \param[in,out] trx Pointer to TRX struct - * \param[in,out] ms_ Pointer to MS object - * \param[in] tbf_ Pointer to TBF struct - * \param[in] res_ul_slots Newly reserved UL slots - * \param[in] res_dl_slots Newly reserved DL slots - * \param[in] ul_slots available UL slots (for logging only) - * \param[in] dl_slots available DL slots (for logging only) - */ -static void update_ms_reserved_slots(gprs_rlcmac_trx *trx, GprsMs *ms, uint8_t res_ul_slots, uint8_t res_dl_slots, - uint8_t ul_slots, uint8_t dl_slots) -{ - char slot_info[9] = { 0 }; - - if (res_ul_slots == ms_reserved_ul_slots(ms) && res_dl_slots == ms_reserved_dl_slots(ms)) - return; - - /* The reserved slots have changed, update the MS */ - ms_set_reserved_slots(ms, trx, res_ul_slots, res_dl_slots); - - ts_format(slot_info, dl_slots, ul_slots); - LOGP(DRLCMAC, LOGL_DEBUG, "- Reserved DL/UL slots: (TS=0)\"%s\"(TS=7)\n", slot_info); -} - -/*! Assign given UL timeslots to UL TBF - * - * \param[in,out] ul_tbf Pointer to UL TBF struct - * \param[in,out] trx Pointer to TRX object - * \param[in] ul_slots Set of slots to be assigned - * \param[in] tfi selected TFI - * \param[in] usf selected USF - */ -static void assign_ul_tbf_slots(struct gprs_rlcmac_ul_tbf *ul_tbf, gprs_rlcmac_trx *trx, uint8_t ul_slots, int tfi, - int *usf) -{ - uint8_t ts; - - for (ts = 0; ts < 8; ts++) { - if (!(ul_slots & (1 << ts))) - continue; - - OSMO_ASSERT(usf[ts] >= 0); - - LOGP(DRLCMAC, LOGL_DEBUG, "- Assigning UL TS %u\n", ts); - assign_uplink_tbf_usf(&trx->pdch[ts], ul_tbf, tfi, usf[ts]); - } -} - -/*! Assign given DL timeslots to DL TBF - * - * \param[in,out] dl_tbf Pointer to DL TBF struct - * \param[in,out] trx Pointer to TRX object - * \param[in] ul_slots Set of slots to be assigned - * \param[in] tfi selected TFI - */ -static void assign_dl_tbf_slots(struct gprs_rlcmac_dl_tbf *dl_tbf, gprs_rlcmac_trx *trx, uint8_t dl_slots, int tfi) -{ - uint8_t ts; - - for (ts = 0; ts < 8; ts++) { - if (!(dl_slots & (1 << ts))) - continue; - - LOGP(DRLCMAC, LOGL_DEBUG, "- Assigning DL TS %u\n", ts); - assign_dlink_tbf(&trx->pdch[ts], dl_tbf, tfi); - } -} - /*! Slot Allocation: Algorithm B * * Assign as many downlink slots as possible. * Assign one uplink slot. (With free USF) * - * \param[in,out] bts Pointer to BTS struct - * \param[in,out] tbf Pointer to TBF struct - * \param[in] single flag indicating if we should force single-slot allocation - * \param[in] use_trx which TRX to use or -1 if it should be selected during allocation + * \param[in] req Contains all the requested params + * \param[out] res The resolution/response for the allocation request * \returns negative error code or 0 on success */ -int alloc_algorithm_b(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, bool single, - int8_t use_trx) +int alloc_algorithm_b(const struct alloc_resources_req *req, + struct alloc_resources_res *res) { uint8_t dl_slots; uint8_t ul_slots; uint8_t reserved_dl_slots; uint8_t reserved_ul_slots; - int8_t first_common_ts; + int8_t first_common_tn; uint8_t slotcount = 0; - uint8_t avail_count = 0, trx_no; - int first_ts = -1; - int usf[8] = {-1, -1, -1, -1, -1, -1, -1, -1}; + uint8_t reserve_count = 0, trx_no; + int first_ts; int rc; int tfi; - struct GprsMs *ms = tbf->ms(); + unsigned int i; gprs_rlcmac_trx *trx; + char slot_info[9] = { 0 }; + struct gprs_rlcmac_pdch *first_common_ts = ms_first_common_ts(req->ms); - LOGPAL(tbf, "B", single, use_trx, LOGL_DEBUG, "Alloc start\n"); + LOGPAL(req, "B", LOGL_DEBUG, "Alloc start\n"); + + for (i = 0; i < ARRAY_SIZE(res->usf); i++) + res->usf[i] = -1; /* Step 1: Get current state from the MS object */ - reserved_dl_slots = ms_reserved_dl_slots(ms); - reserved_ul_slots = ms_reserved_ul_slots(ms); - first_common_ts = ms_first_common_ts(ms); - trx = ms_current_trx(ms); + reserved_dl_slots = ms_reserved_dl_slots(req->ms); + reserved_ul_slots = ms_reserved_ul_slots(req->ms); + first_common_tn = first_common_ts ? first_common_ts->ts_no : -1; /* Step 2a: Find usable TRX and TFI */ - tfi = tfi_find_free(bts, trx, ms, tbf->direction, use_trx, &trx_no); + tfi = tfi_find_free(req, &trx_no); if (tfi < 0) { - LOGPAL(tbf, "B", single, use_trx, LOGL_NOTICE, "failed to allocate a TFI\n"); + LOGPAL(req, "B", LOGL_NOTICE, "failed to allocate a TFI\n"); return tfi; } /* Step 2b: Reserve slots on the TRX for the MS */ - if (!trx) - trx = &bts->trx[trx_no]; + trx = &req->bts->trx[trx_no]; if (!reserved_dl_slots || !reserved_ul_slots) { - rc = find_multi_slots(trx, ms_ms_class(ms), &reserved_ul_slots, &reserved_dl_slots); + rc = find_multi_slots(trx, ms_ms_class(req->ms), &reserved_ul_slots, &reserved_dl_slots); if (rc < 0) return rc; } @@ -906,64 +790,62 @@ int alloc_algorithm_b(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, ul_slots = reserved_ul_slots; /* Step 3a: Derive the slot set for the current TBF */ - rc = tbf_select_slot_set(tbf, trx, single, ul_slots, dl_slots, reserved_ul_slots, reserved_dl_slots, - first_common_ts); + rc = tbf_select_slot_set(req, trx, ul_slots, dl_slots, reserved_ul_slots, reserved_dl_slots, + first_common_tn); if (rc < 0) return -EINVAL; /* Step 3b: Derive the slot set for a given direction */ - if (tbf->direction == GPRS_RLCMAC_DL_TBF) { + if (req->direction == GPRS_RLCMAC_DL_TBF) { dl_slots = rc; - update_slot_counters(dl_slots, reserved_dl_slots, &slotcount, &avail_count); + count_slots(dl_slots, reserved_dl_slots, &slotcount, &reserve_count); } else { - rc = allocate_usf(trx, rc, dl_slots, usf); + rc = allocate_usf(trx, rc, dl_slots, &res->usf[0]); if (rc < 0) return rc; ul_slots = rc; reserved_ul_slots = ul_slots; - update_slot_counters(ul_slots, reserved_ul_slots, &slotcount, &avail_count); + count_slots(ul_slots, reserved_ul_slots, &slotcount, &reserve_count); } first_ts = ffs(rc) - 1; - first_common_ts = ffs(dl_slots & ul_slots) - 1; - - if (first_common_ts < 0) { - LOGPAL(tbf, "B", single, use_trx, LOGL_NOTICE, "first common slot unavailable\n"); + if (first_ts < 0) { + LOGPAL(req, "B", LOGL_NOTICE, "first slot unavailable\n"); return -EINVAL; } - if (first_ts < 0) { - LOGPAL(tbf, "B", single, use_trx, LOGL_NOTICE, "first slot unavailable\n"); + first_common_tn = ffs(dl_slots & ul_slots) - 1; + if (first_common_tn < 0) { + LOGPAL(req, "B", LOGL_NOTICE, "first common slot unavailable\n"); return -EINVAL; } - - if (single && slotcount) { - tbf->upgrade_to_multislot = (avail_count > slotcount); - LOGPAL(tbf, "B", single, use_trx, LOGL_INFO, "using single slot at TS %d\n", first_ts); + first_common_ts = &trx->pdch[first_common_tn]; + + res->trx = trx; + res->first_common_ts = first_common_ts; + res->reserved_ul_slots = reserved_ul_slots; + res->reserved_dl_slots = reserved_dl_slots; + res->tfi = tfi; + /* res->usf is already filled in above */ + if (req->single && slotcount) { + res->upgrade_to_multislot = (reserve_count > slotcount); + LOGPAL(req, "B", LOGL_INFO, "using single slot at TS %d\n", first_ts); } else { - tbf->upgrade_to_multislot = false; - LOGPAL(tbf, "B", single, use_trx, LOGL_INFO, "using %d slots\n", slotcount); + res->upgrade_to_multislot = false; + LOGPAL(req, "B", LOGL_INFO, "using %d slots\n", slotcount); } - /* The allocation will be successful, so the system state and tbf/ms - * may be modified from now on. */ - - /* Step 4: Update MS and TBF and really allocate the resources */ - - update_ms_reserved_slots(trx, ms, reserved_ul_slots, reserved_dl_slots, ul_slots, dl_slots); - - tbf->trx = trx; - tbf->first_common_ts = first_common_ts; - tbf->first_ts = first_ts; + ts_format(slot_info, dl_slots, ul_slots); + LOGP(DRLCMAC, LOGL_DEBUG, "- Available DL/UL slots: (TS=0)\"%s\"(TS=7)\n", slot_info); - if (tbf->direction == GPRS_RLCMAC_DL_TBF) - assign_dl_tbf_slots(as_dl_tbf(tbf), trx, dl_slots, tfi); + if (req->direction == GPRS_RLCMAC_DL_TBF) + res->ass_slots_mask = dl_slots; else - assign_ul_tbf_slots(as_ul_tbf(tbf), trx, ul_slots, tfi, usf); + res->ass_slots_mask = ul_slots; - bts_do_rate_ctr_inc(bts, CTR_TBF_ALLOC_ALGO_B); + bts_do_rate_ctr_inc(req->bts, CTR_TBF_ALLOC_ALGO_B); return 0; } @@ -976,35 +858,33 @@ int alloc_algorithm_b(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, * The goal is to support as many MS and TBF as possible. On low usage, the * goal is to provide the highest possible bandwidth per MS. * - * \param[in,out] bts Pointer to BTS struct - * \param[in,out] tbf Pointer to TBF struct - * \param[in] single flag indicating if we should force single-slot allocation - * \param[in] use_trx which TRX to use or -1 if it should be selected during allocation + * \param[in] req Contains all the requested params + * \param[out] res The resolution/response for the allocation request * \returns negative error code or 0 on success */ -int alloc_algorithm_dynamic(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, bool single, - int8_t use_trx) +int alloc_algorithm_dynamic(const struct alloc_resources_req *req, + struct alloc_resources_res *res) { int rc; /* Reset load_is_high if there is at least one idle PDCH */ - if (bts->multislot_disabled) { - bts->multislot_disabled = !idle_pdch_avail(bts); - if (!bts->multislot_disabled) + if (req->bts->multislot_disabled) { + req->bts->multislot_disabled = !idle_pdch_avail(req->bts); + if (!req->bts->multislot_disabled) LOGP(DRLCMAC, LOGL_DEBUG, "Enabling algorithm B\n"); } - if (!bts->multislot_disabled) { - rc = alloc_algorithm_b(bts, tbf, single, use_trx); + if (!req->bts->multislot_disabled) { + rc = alloc_algorithm_b(req, res); if (rc >= 0) return rc; - if (!bts->multislot_disabled) + if (!req->bts->multislot_disabled) LOGP(DRLCMAC, LOGL_DEBUG, "Disabling algorithm B\n"); - bts->multislot_disabled = 1; + req->bts->multislot_disabled = 1; } - return alloc_algorithm_a(bts, tbf, single, use_trx); + return alloc_algorithm_a(req, res); } int gprs_alloc_max_dl_slots_per_ms(const struct gprs_rlcmac_bts *bts, uint8_t ms_class) diff --git a/src/alloc_algo.h b/src/alloc_algo.h new file mode 100644 index 00000000..f863abd7 --- /dev/null +++ b/src/alloc_algo.h @@ -0,0 +1,67 @@ +/* alloc_algo.h + * + * Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +#include "tbf.h" + +struct gprs_rlcmac_bts; +struct GprsMs; +struct gprs_rlcmac_tbf; + +#ifdef __cplusplus +extern "C" { +#endif + +struct alloc_resources_req { + /* BTS where to allocate resources */ + struct gprs_rlcmac_bts *bts; + /* MS for which to allocate resources */ + const struct GprsMs *ms; + /* Direction of the TBF for which we are allocating resources */ + enum gprs_rlcmac_tbf_direction direction; + /* Whether to allocate only a single (1) TS */ + bool single; + /* Whether to allocate on a specific TRX (>=0) or not (-1) */ + int8_t use_trx; +}; + +struct alloc_resources_res { + struct gprs_rlcmac_trx *trx; + struct gprs_rlcmac_pdch *first_common_ts; + uint8_t reserved_ul_slots; + uint8_t reserved_dl_slots; + uint8_t ass_slots_mask; + bool upgrade_to_multislot; + uint8_t tfi; + int usf[8]; +}; + +int alloc_algorithm_a(const struct alloc_resources_req *req, + struct alloc_resources_res *res); + +int alloc_algorithm_b(const struct alloc_resources_req *req, + struct alloc_resources_res *res); + +int alloc_algorithm_dynamic(const struct alloc_resources_req *req, + struct alloc_resources_res *res); +int gprs_alloc_max_dl_slots_per_ms(const struct gprs_rlcmac_bts *bts, uint8_t ms_class); + +#ifdef __cplusplus +} +#endif diff --git a/src/bts.cpp b/src/bts.cpp index 50df92e2..bad06a52 100644 --- a/src/bts.cpp +++ b/src/bts.cpp @@ -4,8 +4,8 @@ * 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 + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, @@ -13,7 +13,7 @@ * 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 + * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ @@ -30,7 +30,6 @@ #include <gprs_debug.h> #include <cxx_linuxlist.h> #include <pdch.h> -#include <gprs_ms_storage.h> #include <sba.h> #include <bts_pch_timer.h> @@ -40,6 +39,7 @@ extern "C" { #include <osmocom/core/stats.h> #include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/gsm/gsm_utils.h> + #include <osmocom/gsm/gsm0502.h> #include <osmocom/gsm/gsm48.h> #include <osmocom/core/gsmtap_util.h> #include <osmocom/core/application.h> @@ -52,7 +52,6 @@ extern "C" { #include <errno.h> #include <string.h> -#define RFN_MODULUS 42432 #define RFN_THRESHOLD RFN_MODULUS / 2 extern void *tall_pcu_ctx; @@ -78,11 +77,26 @@ void bts_trx_free_all_tbf(struct gprs_rlcmac_trx *trx) static struct osmo_tdef T_defs_bts[] = { { .T=3142, .default_val=20, .unit=OSMO_TDEF_S, .desc="Wait Indication used in Imm Ass Reject during TBF Establishment (CCCH)", .val=0, .min_val = 0, .max_val = 255 }, /* TS 44.018 10.5.2.43, TS 44.060 7.1.3.2.1 (T3172) */ + { .T=3168, .default_val=4000, .unit=OSMO_TDEF_MS, .desc="Time MS waits for PACKET UPLINK ACK when establishing a UL TBF", .val=0 }, { .T=3169, .default_val=5, .unit=OSMO_TDEF_S, .desc="Reuse of USF and TFI(s) after the MS uplink TBF assignment is invalid", .val=0 }, - { .T=3172, .default_val=5000,.unit=OSMO_TDEF_MS, .desc="Wait Indication used in Imm Ass Reject during TBF Establishment (PACCH)", .val=0, .min_val = 0, .max_val = 255000 }, /* TS 44.060 7.1.3.2.1 */ { .T=3191, .default_val=5, .unit=OSMO_TDEF_S, .desc="Reuse of TFI(s) after sending (1) last RLC Data Block on TBF(s), or (2) PACKET TBF RELEASE for an MBMS radio bearer", .val=0 }, - { .T=3193, .default_val=100, .unit=OSMO_TDEF_MS, .desc="Reuse of TFI(s) after reception of final PACKET DOWNLINK ACK/NACK from MS for TBF", .val=0 }, + { .T=3192, .default_val=1500, .unit=OSMO_TDEF_MS, .desc="Time MS stays monitoring the PDCH after transmitting DL ACK/NACK for last DL block (FBI=1). Configured at the BSC (SI13).", .val=0 }, + { .T=3193, .default_val=1600, .unit=OSMO_TDEF_MS, .desc="Reuse of TFI(s) after reception of final PACKET DOWNLINK ACK/NACK from MS for TBF", .val=0 }, { .T=3195, .default_val=5, .unit=OSMO_TDEF_S, .desc="Reuse of TFI(s) upon no response from the MS (radio failure or cell change) for TBF/MBMS radio bearer", .val=0 }, + { .T = -16, .default_val = 1000, .unit = OSMO_TDEF_MS, + .desc = "Granularity for *:all_allocated rate counters: amount of milliseconds that one counter increment" + " represents. See also X17, X18" }, + { .T = -17, .default_val = 0, .unit = OSMO_TDEF_MS, + .desc = "Rounding threshold for *:all_allocated rate counters: round up to the next counter increment" + " after this many milliseconds. If set to half of X16 (or 0), employ the usual round() behavior:" + " round up after half of a granularity period. If set to 1, behave like ceil(): already" + " increment the counter immediately when all channels are allocated. If set >= X16, behave like" + " floor(): only increment after a full X16 period of all channels being occupied." + " See also X16, X18" }, + { .T = -18, .default_val = 60000, .unit = OSMO_TDEF_MS, + .desc = "Forget-sum period for *:all_allocated rate counters:" + " after this amount of idle time, forget internally cumulated time remainders. Zero to always" + " keep remainders. See also X16, X17." }, { .T=0, .default_val=0, .unit=OSMO_TDEF_S, .desc=NULL, .val=0 } /* empty item at the end */ }; @@ -92,6 +106,7 @@ static struct osmo_tdef T_defs_bts[] = { * the code below. */ static const struct rate_ctr_desc bts_ctr_description[] = { + { "pdch:all_allocated", "Cumulative counter of seconds where all enabled PDCH resources were allocated"}, { "tbf:dl:alloc", "TBF DL Allocated "}, { "tbf:dl:freed", "TBF DL Freed "}, { "tbf:dl:aborted", "TBF DL Aborted "}, @@ -134,14 +149,21 @@ static const struct rate_ctr_desc bts_ctr_description[] = { { "llc:dl_bytes", "RLC encapsulated PDUs"}, { "llc:ul_bytes", "full PDUs received "}, { "pch:requests", "PCH requests sent "}, + { "pch:requests:already", "PCH requests on subscriber already being paged"}, { "pch:requests:timeout", "PCH requests timeout "}, { "rach:requests", "RACH requests received"}, - { "11bit_rach:requests", "11BIT_RACH requests received"}, + { "rach:requests:11bit", "11BIT_RACH requests received"}, + { "rach:requests:one_phase", "One phase packet access with request for single TS UL"}, /* TS 52.402 B.2.1.49 */ + { "rach:requests:two_phase", "Single block packet request for two phase packet access"}, /* TS 52.402 B.2.1.49 */ + { "rach:requests:unexpected", "RACH Request with unexpected content received"}, { "spb:uplink_first_segment", "First seg of UL SPB "}, { "spb:uplink_second_segment", "Second seg of UL SPB "}, { "spb:downlink_first_segment", "First seg of DL SPB "}, { "spb:downlink_second_segment","Second seg of DL SPB "}, { "immediate:assignment_UL", "Immediate Assign UL "}, + { "immediate:assignment_ul:one_phase", "Immediate Assign UL (one phase packet access)"}, /* TS 52.402 B.2.1.50 */ + { "immediate:assignment_ul:two_phase", "Immediate Assign UL (two phase packet access)"}, /* TS 52.402 B.2.1.50 */ + { "immediate:assignment_ul:contention_resolution_success", "First RLC Block (PDU) on the PDTCH from the MS received"}, /* TS 52.402 B.2.1.51 */ { "immediate:assignment_rej", "Immediate Assign Rej "}, { "immediate:assignment_DL", "Immediate Assign DL "}, { "channel:request_description","Channel Request Desc "}, @@ -219,10 +241,13 @@ static const struct osmo_stat_item_group_desc bts_statg_desc = { static int bts_talloc_destructor(struct gprs_rlcmac_bts* bts) { - /* this can cause counter updates and must not be left to the - * m_ms_store's destructor */ - bts->ms_store->cleanup(); - delete bts->ms_store; + struct GprsMs *ms; + while ((ms = llist_first_entry_or_null(&bts->ms_list, struct GprsMs, list))) + talloc_free(ms); + + gprs_bssgp_destroy(bts); + + osmo_time_cc_cleanup(&bts->all_allocated_pdch); if (bts->ratectrs) { rate_ctr_group_free(bts->ratectrs); @@ -256,8 +281,6 @@ struct gprs_rlcmac_bts* bts_alloc(struct gprs_pcu *pcu, uint8_t bts_nr) bts->pcu = pcu; bts->nr = bts_nr; - bts->ms_store = new GprsMsStorage(bts); - bts->cur_fn = FN_UNSET; bts->cur_blk_fn = -1; bts->max_cs_dl = MAX_GPRS_CS; @@ -293,22 +316,37 @@ struct gprs_rlcmac_bts* bts_alloc(struct gprs_pcu *pcu, uint8_t bts_nr) bts->statg = osmo_stat_item_group_alloc(tall_pcu_ctx, &bts_statg_desc, 0); OSMO_ASSERT(bts->statg); + osmo_time_cc_init(&bts->all_allocated_pdch); + struct osmo_time_cc_cfg *cc_cfg = &bts->all_allocated_pdch.cfg; + cc_cfg->gran_usec = 1*1000000, + cc_cfg->forget_sum_usec = 60*1000000, + cc_cfg->rate_ctr = rate_ctr_group_get_ctr(bts->ratectrs, CTR_PDCH_ALL_ALLOCATED), + cc_cfg->T_gran = -16, + cc_cfg->T_round_threshold = -17, + cc_cfg->T_forget_sum = -18, + cc_cfg->T_defs = T_defs_bts, + llist_add_tail(&bts->list, &pcu->bts_list); INIT_LLIST_HEAD(&bts->pch_timer); + INIT_LLIST_HEAD(&bts->ms_list); return bts; } void bts_set_current_frame_number(struct gprs_rlcmac_bts *bts, uint32_t fn) { + /* See also 3GPP TS 45.002, section 4.3.3 */ + OSMO_ASSERT(fn < GSM_TDMA_HYPERFRAME); + /* The UL frame numbers lag 3 behind the DL frames and the data * indication is only sent after all 4 frames of the block have been * received. Sometimes there is an idle frame between the end of one * and start of another frame (every 3 blocks). */ if (fn != bts->cur_fn && bts->cur_fn != FN_UNSET && fn != fn_next_block(bts->cur_fn)) { LOGP(DRLCMAC, LOGL_NOTICE, - "Detected FN jump! %u -> %u\n", bts->cur_fn, fn); + "Detected FN jump! %u -> %u (expected %u, delta %u)\n", + bts->cur_fn, fn, fn_next_block(bts->cur_fn), GSM_TDMA_FN_DIFF(bts->cur_fn, fn)); } bts->cur_fn = fn; } @@ -387,7 +425,7 @@ int bts_add_paging(struct gprs_rlcmac_bts *bts, const struct paging_req_cs *req, struct gprs_rlcmac_tbf *tbf; struct llist_head *tmp; const struct osmo_mobile_identity *mi; - uint8_t slot_mask[8]; + uint8_t slot_mask[ARRAY_SIZE(bts->trx)]; int8_t first_ts; /* must be signed */ /* First, build the MI used to page on PDCH from available subscriber info: */ @@ -420,11 +458,11 @@ int bts_add_paging(struct gprs_rlcmac_bts *bts, const struct paging_req_cs *req, * Don't mark, if TBF uses a different slot that is already marked. */ memset(slot_mask, 0, sizeof(slot_mask)); - llist_for_each(tmp, bts_ms_store(bts)->ms_list()) { + llist_for_each(tmp, &bts->ms_list) { ms = llist_entry(tmp, typeof(*ms), list); struct gprs_rlcmac_tbf *tbfs[] = { ms->ul_tbf, ms->dl_tbf }; for (l = 0; l < ARRAY_SIZE(tbfs); l++) { - tbf = (struct gprs_rlcmac_tbf *)tbfs[l]; + tbf = tbfs[l]; if (!tbf) continue; first_ts = -1; @@ -495,7 +533,7 @@ void bts_send_gsmtap_rach(struct gprs_rlcmac_bts *bts, } bts_send_gsmtap_meas(bts, categ, true, rip->trx_nr, rip->ts_nr, channel, - bts_rfn_to_fn(bts, rip->rfn), ra_buf, + rip->fn, ra_buf, rip->is_11bit ? 2 : 1, &meas); } @@ -595,7 +633,7 @@ int bts_tfi_find_free(const struct gprs_rlcmac_bts *bts, enum gprs_rlcmac_tbf_di unsigned int best_cnt = 0; uint8_t best_first_tfi = 0; - if (use_trx >= 0 && use_trx < 8) + if (use_trx >= 0 && use_trx < (int8_t)ARRAY_SIZE(bts->trx)) trx_from = trx_to = use_trx; else { trx_from = 0; @@ -604,7 +642,7 @@ int bts_tfi_find_free(const struct gprs_rlcmac_bts *bts, enum gprs_rlcmac_tbf_di /* find a TFI that is unused on all PDCH */ for (trx = trx_from; trx <= trx_to; trx++) { - uint8_t tmp_first_tfi; + uint8_t tmp_first_tfi = 0xff; /* make gcc happy */ unsigned int tmp_cnt; tmp_cnt = trx_count_free_tfi(&bts->trx[trx], dir, &tmp_first_tfi); if (tmp_cnt > best_cnt) { @@ -628,100 +666,125 @@ int bts_tfi_find_free(const struct gprs_rlcmac_bts *bts, enum gprs_rlcmac_tbf_di return best_first_tfi; } -int bts_rcv_imm_ass_cnf(struct gprs_rlcmac_bts *bts, const uint8_t *data, uint32_t fn) +static int tlli_from_imm_ass(uint32_t *tlli, const uint8_t *data) { - struct gprs_rlcmac_dl_tbf *dl_tbf = NULL; + const struct gsm48_imm_ass *imm_ass = (struct gsm48_imm_ass *)data; uint8_t plen; - uint32_t tlli; - GprsMs *ms; - /* move to IA Rest Octets */ - plen = data[0] >> 2; + /* Move to IA Rest Octets: TS 44.018 9.1.18 "The L2 pseudo length of + * this message is the sum of lengths of all information elements + * present in the message except the IA Rest Octets and L2 Pseudo Length + * information elements." */ + /* TS 44.018 10.5.2.19 l2_plen byte lowest 2 bits are '01'B */ + plen = imm_ass->l2_plen >> 2; data += 1 + plen; if ((*data & 0xf0) != 0xd0) { - LOGP(DRLCMAC, LOGL_ERROR, "Got IMM.ASS confirm, but rest " + LOGP(DTBFDL, LOGL_ERROR, "Got IMM.ASS confirm, but rest " "octets do not start with bit sequence 'HH01' " "(Packet Downlink Assignment)\n"); return -EINVAL; } /* get TLLI from downlink assignment */ - tlli = (uint32_t)((*data++) & 0xf) << 28; - tlli |= (*data++) << 20; - tlli |= (*data++) << 12; - tlli |= (*data++) << 4; - tlli |= (*data++) >> 4; - - ms = bts_ms_by_tlli(bts, tlli, GSM_RESERVED_TMSI); - if (ms) - dl_tbf = ms_dl_tbf(ms); + *tlli = (uint32_t)((*data++) & 0xf) << 28; + *tlli |= (*data++) << 20; + *tlli |= (*data++) << 12; + *tlli |= (*data++) << 4; + *tlli |= (*data++) >> 4; + + return 0; +} + +int bts_rcv_imm_ass_cnf(struct gprs_rlcmac_bts *bts, const uint8_t *data, uint32_t tlli) +{ + struct gprs_rlcmac_dl_tbf *dl_tbf; + GprsMs *ms; + int rc; + + /* NOTE: A confirmation for a downlink IMMEDIATE ASSIGNMENT can be received using two different methods. + * One way is to send the whole IMMEDIATE ASSIGNMENT back to the PCU and the TLLI, which we use as + * reference is extracted from the rest octets of this message. Alternatively the TLLI may be sent as + * confirmation directly. */ + + /* Extract TLLI from the presented IMMEDIATE ASSIGNMENT + * (if present and only when TLLI that is supplied as function parameter is valid.) */ + if (data && tlli == GSM_RESERVED_TMSI) { + rc = tlli_from_imm_ass(&tlli, data); + if (rc != 0) + return -EINVAL; + } + + /* Make sure TLLI is valid */ + if (tlli == GSM_RESERVED_TMSI) { + LOGP(DTBFDL, LOGL_ERROR, "Got IMM.ASS confirm, but TLLI is invalid!\n"); + return -EINVAL; + } + + /* Find related TBF and send confirmation signal to FSM */ + ms = bts_get_ms_by_tlli(bts, tlli, GSM_RESERVED_TMSI); + if (!ms) { + LOGP(DTBFDL, LOGL_ERROR, "Got IMM.ASS confirm for unknown MS with TLLI=%08x\n", tlli); + return -EINVAL; + } + dl_tbf = ms_dl_tbf(ms); if (!dl_tbf) { - LOGP(DRLCMAC, LOGL_ERROR, "Got IMM.ASS confirm, but TLLI=%08x " - "does not exit\n", tlli); + LOGPMS(ms, DTBFDL, LOGL_ERROR, "Got IMM.ASS confirm, but MS has no DL TBF!\n"); return -EINVAL; } - LOGP(DRLCMAC, LOGL_DEBUG, "Got IMM.ASS confirm for TLLI=%08x\n", tlli); - osmo_fsm_inst_dispatch(dl_tbf->state_fsm.fi, TBF_EV_ASSIGN_PCUIF_CNF, NULL); + LOGPTBFDL(dl_tbf, LOGL_DEBUG, "Got IMM.ASS confirm\n"); + osmo_fsm_inst_dispatch(dl_tbf->state_fi, TBF_EV_ASSIGN_PCUIF_CNF, NULL); return 0; } /* Determine the full frame number from a relative frame number */ -uint32_t bts_rfn_to_fn(const struct gprs_rlcmac_bts *bts, int32_t rfn) +uint32_t bts_rfn_to_fn(const struct gprs_rlcmac_bts *bts, uint32_t rfn) { - int32_t m_cur_rfn; - int32_t fn; - int32_t fn_rounded; - - /* double-check that relative FN is not negative and fits into int32_t */ - OSMO_ASSERT(rfn < GSM_MAX_FN); - OSMO_ASSERT(rfn >= 0); - - /* Note: If a BTS is sending in a rach request it will be fully aware - * of the frame number. If the PCU is used in a BSC-co-located setup. - * The BSC will forward the incoming RACH request. The RACH request - * only contains the relative frame number (Fn % 42432) in its request - * reference. This PCU implementation has to fit both scenarios, so - * we need to assume that Fn is a relative frame number. */ + uint32_t m_cur_fn, m_cur_rfn; + uint32_t fn_rounded; /* Ensure that all following calculations are performed with the * relative frame number */ - if (rfn >= RFN_MODULUS) + OSMO_ASSERT(rfn < RFN_MODULUS); + + m_cur_fn = bts_current_frame_number(bts); + if (OSMO_UNLIKELY(m_cur_fn == FN_UNSET)) { + LOGP(DRLCMAC, LOGL_ERROR, "Unable to calculate full FN from RFN %u: Current FN not known!\n", + rfn); return rfn; + } /* Compute an internal relative frame number from the full internal frame number */ - m_cur_rfn = bts->cur_fn % RFN_MODULUS; + m_cur_rfn = fn2rfn(m_cur_fn); /* Compute a "rounded" version of the internal frame number, which * exactly fits in the RFN_MODULUS raster */ - fn_rounded = bts->cur_fn - m_cur_rfn; + fn_rounded = GSM_TDMA_FN_SUB(m_cur_fn, m_cur_rfn); /* If the delta between the internal and the external relative frame * number exceeds a certain limit, we need to assume that the incoming * rach request belongs to a the previous rfn period. To correct this, * we roll back the rounded frame number by one RFN_MODULUS */ - if (abs(rfn - m_cur_rfn) > RFN_THRESHOLD) { + if (GSM_TDMA_FN_DIFF(rfn, m_cur_rfn) > RFN_THRESHOLD) { LOGP(DRLCMAC, LOGL_DEBUG, "Race condition between rfn (%u) and m_cur_fn (%u) detected: rfn belongs to the previous modulus %u cycle, wrapping...\n", - rfn, bts->cur_fn, RFN_MODULUS); + rfn, m_cur_fn, RFN_MODULUS); if (fn_rounded < RFN_MODULUS) { LOGP(DRLCMAC, LOGL_DEBUG, "Cornercase detected: wrapping crosses %u border\n", GSM_MAX_FN); - fn_rounded = GSM_MAX_FN - (RFN_MODULUS - fn_rounded); + fn_rounded = GSM_TDMA_FN_SUB(GSM_MAX_FN, (GSM_TDMA_FN_SUB(RFN_MODULUS, fn_rounded))); } else - fn_rounded -= RFN_MODULUS; + fn_rounded = GSM_TDMA_FN_SUB(fn_rounded, RFN_MODULUS); } /* The real frame number is the sum of the rounded frame number and the * relative framenumber computed via RACH */ - fn = fn_rounded + rfn; - - return fn; + return GSM_TDMA_FN_SUM(fn_rounded, rfn); } /* 3GPP TS 44.060: @@ -880,29 +943,42 @@ int bts_rcv_rach(struct gprs_rlcmac_bts *bts, const struct rach_ind_params *rip) bts_do_rate_ctr_inc(bts, CTR_RACH_REQUESTS); if (rip->is_11bit) - bts_do_rate_ctr_inc(bts, CTR_11BIT_RACH_REQUESTS); + bts_do_rate_ctr_inc(bts, CTR_RACH_REQUESTS_11BIT); - /* Determine full frame number */ - uint32_t Fn = bts_rfn_to_fn(bts, rip->rfn); uint8_t ta = qta2ta(rip->qta); bts_send_gsmtap_rach(bts, PCU_GSMTAP_C_UL_RACH, GSMTAP_CHANNEL_RACH, rip); LOGP(DRLCMAC, LOGL_DEBUG, "MS requests Uplink resource on CCCH/RACH: " "ra=0x%02x (%d bit) Fn=%u qta=%d\n", rip->ra, - rip->is_11bit ? 11 : 8, Fn, rip->qta); + rip->is_11bit ? 11 : 8, rip->fn, rip->qta); /* Parse [EGPRS Packet] Channel Request from RACH.ind */ rc = parse_rach_ind(rip, &chan_req); - if (rc) /* Send RR Immediate Assignment Reject */ + if (rc) { + bts_do_rate_ctr_inc(bts, CTR_RACH_REQUESTS_UNEXPECTED); + /* Send RR Immediate Assignment Reject */ goto send_imm_ass_rej; + } - if (chan_req.single_block) - LOGP(DRLCMAC, LOGL_DEBUG, "MS requests single block allocation\n"); - else if (bts->pcu->vty.force_two_phase) { - LOGP(DRLCMAC, LOGL_DEBUG, "MS requests single block allocation, " - "but we force two phase access\n"); - chan_req.single_block = true; + if (chan_req.single_block) { + bts_do_rate_ctr_inc(bts, CTR_RACH_REQUESTS_TWO_PHASE); + LOGP(DRLCMAC, LOGL_DEBUG, "MS requests single block allocation " + "(two phase packet access)\n"); + } else { + bts_do_rate_ctr_inc(bts, CTR_RACH_REQUESTS_ONE_PHASE); + LOGP(DRLCMAC, LOGL_DEBUG, "MS requests single TS uplink transmission " + "(one phase packet access)\n"); + if (bts->pcu->vty.force_two_phase) { + /* 3GPP TS 44.018 3.5.2.1.3.1: "If the establishment cause in the + * CHANNEL REQUEST message indicates a request for one phase packet access, + * the network may grant either a one phase packet access or a single block + * packet access for the mobile station. If a single block packet access is + * granted, it forces the mobile station to perform a two phase packet access." + */ + LOGP(DRLCMAC, LOGL_DEBUG, "Forcing two phase access\n"); + chan_req.single_block = true; + } } /* TODO: handle Radio Priority (see 3GPP TS 44.060, table 11.2.5a.5) */ @@ -925,17 +1001,25 @@ int bts_rcv_rach(struct gprs_rlcmac_bts *bts, const struct rach_ind_params *rip) sb_fn = sba->fn; LOGP(DRLCMAC, LOGL_DEBUG, "Allocated a single block at " "SBFn=%u TRX=%u TS=%u\n", sb_fn, pdch->trx->trx_no, pdch->ts_no); + bts_do_rate_ctr_inc(bts, CTR_IMMEDIATE_ASSIGN_UL_TBF_TWO_PHASE); } else { - GprsMs *ms = bts_alloc_ms(bts, 0, chan_req.egprs_mslot_class); - tbf = tbf_alloc_ul_ccch(bts, ms); + GprsMs *ms = ms_alloc(bts, __func__); + ms_set_egprs_ms_class(ms, chan_req.egprs_mslot_class); + tbf = ms_new_ul_tbf_assigned_agch(ms); + /* Here either tbf was created and it holds a ref to MS, or tbf + * creation failed and MS will end up without references and being + * freed: */ + ms_unref(ms, __func__); if (!tbf) { /* Send RR Immediate Assignment Reject */ rc = -EBUSY; goto send_imm_ass_rej; } tbf->set_ta(ta); - pdch = &tbf->trx->pdch[tbf->first_ts]; + /* Only single TS can be allocated through AGCH, hence first TS is the only one: */ + pdch = tbf_get_first_ts(tbf); usf = tbf->m_usf[pdch->ts_no]; + bts_do_rate_ctr_inc(bts, CTR_IMMEDIATE_ASSIGN_UL_TBF_ONE_PHASE); } trx = pdch->trx; @@ -944,12 +1028,12 @@ int bts_rcv_rach(struct gprs_rlcmac_bts *bts, const struct rach_ind_params *rip) trx->trx_no, trx->arfcn & ~ARFCN_FLAG_MASK, pdch->ts_no, ta, pdch->tsc, tbf ? tbf->tfi() : -1, usf); plen = Encoding::write_immediate_assignment(pdch, tbf, bv, - false, rip->ra, Fn, ta, usf, false, sb_fn, + false, rip->ra, rip->rfn, ta, usf, false, fn2rfn(sb_fn), bts_get_ms_pwr_alpha(bts), bts->pcu->vty.gamma, -1, rip->burst_type); bts_do_rate_ctr_inc(bts, CTR_IMMEDIATE_ASSIGN_UL_TBF); if (plen >= 0) { - pcu_l1if_tx_agch(bts, bv, plen); + pcu_l1if_tx_agch2(bts, bv, plen, false, GSM_RESERVED_TMSI); rc = 0; } else { rc = plen; @@ -960,11 +1044,11 @@ int bts_rcv_rach(struct gprs_rlcmac_bts *bts, const struct rach_ind_params *rip) send_imm_ass_rej: LOGP(DRLCMAC, LOGL_DEBUG, "Tx Immediate Assignment Reject on AGCH\n"); plen = Encoding::write_immediate_assignment_reject( - bv, rip->ra, Fn, rip->burst_type, + bv, rip->ra, rip->rfn, rip->burst_type, (uint8_t)osmo_tdef_get(bts->T_defs_bts, 3142, OSMO_TDEF_S, -1)); bts_do_rate_ctr_inc(bts, CTR_IMMEDIATE_ASSIGN_REJ); if (plen >= 0) - pcu_l1if_tx_agch(bts, bv, plen); + pcu_l1if_tx_agch2(bts, bv, plen, false, GSM_RESERVED_TMSI); bitvec_free(bv); /* rc was already properly set before goto */ return rc; @@ -980,7 +1064,7 @@ static uint32_t ptcch_slot_map[PTCCH_TAI_NUM] = { int bts_rcv_ptcch_rach(struct gprs_rlcmac_bts *bts, const struct rach_ind_params *rip) { - uint32_t fn416 = bts_rfn_to_fn(bts, rip->rfn) % 416; + uint16_t fn416 = rip->rfn % 416; struct gprs_rlcmac_pdch *pdch; uint8_t ss; @@ -1019,28 +1103,47 @@ int bts_rcv_ptcch_rach(struct gprs_rlcmac_bts *bts, const struct rach_ind_params return 0; } -void bts_snd_dl_ass(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, uint16_t pgroup) +void bts_snd_dl_ass(struct gprs_rlcmac_bts *bts, const struct gprs_rlcmac_dl_tbf *tbf) { - uint8_t trx_no = tbf->trx->trx_no; - uint8_t ts_no = tbf->first_ts; int plen; - LOGPTBF(tbf, LOGL_INFO, "TX: START Immediate Assignment Downlink (PCH)\n"); + /* Only one TS can be assigned through PCH, hence the first one is the only one: */ + const struct gprs_rlcmac_pdch *pdch = tbf_get_first_ts_const(tbf); + OSMO_ASSERT(pdch); + + LOGPTBFDL(tbf, LOGL_INFO, "Tx CCCH (PCH) Immediate Assignment [PktDlAss=%s] TA=%d\n", pdch_name(pdch), tbf->ta()); + bitvec *immediate_assignment = bitvec_alloc(22, tall_pcu_ctx); /* without plen */ bitvec_unhex(immediate_assignment, DUMMY_VEC); /* standard '2B'O padding */ - /* use request reference that has maximum distance to current time, - * so the assignment will not conflict with possible RACH requests. */ - LOGP(DRLCMAC, LOGL_DEBUG, " - TRX=%d (%d) TS=%d TA=%d\n", - trx_no, tbf->trx->arfcn, ts_no, tbf->ta()); - plen = Encoding::write_immediate_assignment(&bts->trx[trx_no].pdch[ts_no], + /* 3GPP TS 44.018, section 9.1.18.0d states that the network shall code the + * Request Reference IE, e.g. by using a suitably offset frame number, such + * that the resource reference cannot be confused with any CHANNEL REQUEST + * message sent by a mobile station. Use last_rts_fn + 21216 (16 TDMA + * super-frame periods, or ~21.3 seconds) to achieve a decent distance. */ + plen = Encoding::write_immediate_assignment(pdch, tbf, immediate_assignment, true, 125, - (tbf->pdch[ts_no]->last_rts_fn + 21216) % GSM_MAX_FN, + fn2rfn(GSM_TDMA_FN_SUM(pdch->last_rts_fn, 21216)), tbf->ta(), 7, false, 0, bts_get_ms_pwr_alpha(bts), bts->pcu->vty.gamma, -1, GSM_L1_BURST_TYPE_ACCESS_0); if (plen >= 0) { bts_do_rate_ctr_inc(bts, CTR_IMMEDIATE_ASSIGN_DL_TBF); - pcu_l1if_tx_pch(bts, immediate_assignment, plen, pgroup); + + if (ms_imsi_is_valid(tbf->ms())) { + pcu_l1if_tx_pch2(bts, immediate_assignment, plen, true, tbf->imsi(), tbf->tlli()); + } else { + /* During GMM ATTACH REQUEST, the IMSI is not yet known to the PCU or SGSN. (It is + * requested after the GMM ATTACH REQUEST with the GMM IDENTITY REQUEST.) When the PCU + * has to assign a DL TBF but the IMSI is not known, then the IMMEDIATE ASSIGNMENT is + * sent on the AGCH. The reason for this is that without an IMSI we can not calculate + * the paging group, which would be necessary for transmission on PCH. Since the IMSI + * is usually only unknown during the GMM ATTACH REQUEST, we may assume that the MS + * is in non-DRX mode and hence it is listening on all CCCH blocks, including AGCH. + * + * See also: 3gpp TS 44.060, section 5.5.1.5 + * 3gpp TS 45.002, section 6.5.3, 6.5.6 */ + pcu_l1if_tx_agch2(bts, immediate_assignment, plen, true, tbf->tlli()); + } } bitvec_free(immediate_assignment); @@ -1105,26 +1208,42 @@ bool bts_cs_dl_is_supported(const struct gprs_rlcmac_bts* bts, CodingScheme cs) } } -GprsMs *bts_alloc_ms(struct gprs_rlcmac_bts* bts, uint8_t ms_class, uint8_t egprs_ms_class) +struct GprsMs *bts_get_ms(const struct gprs_rlcmac_bts *bts, uint32_t tlli, uint32_t old_tlli, + const char *imsi) { - GprsMs *ms; - ms = bts_ms_store(bts)->create_ms(); + struct llist_head *tmp; - ms_set_timeout(ms, osmo_tdef_get(bts->pcu->T_defs, -2030, OSMO_TDEF_S, -1)); - ms_set_ms_class(ms, ms_class); - ms_set_egprs_ms_class(ms, egprs_ms_class); + if (tlli != GSM_RESERVED_TMSI || old_tlli != GSM_RESERVED_TMSI) { + llist_for_each(tmp, &bts->ms_list) { + struct GprsMs *ms = llist_entry(tmp, typeof(*ms), list); + if (ms_check_tlli(ms, tlli)) + return ms; + if (ms_check_tlli(ms, old_tlli)) + return ms; + } + } - return ms; + /* not found by TLLI */ + + if (imsi && imsi[0] != '\0') { + llist_for_each(tmp, &bts->ms_list) { + struct GprsMs *ms = llist_entry(tmp, typeof(*ms), list); + if (ms_imsi_is_valid(ms) && strcmp(imsi, ms_imsi(ms)) == 0) + return ms; + } + } + + return NULL; } -struct GprsMsStorage *bts_ms_store(const struct gprs_rlcmac_bts *bts) +struct GprsMs *bts_get_ms_by_tlli(const struct gprs_rlcmac_bts *bts, uint32_t tlli, uint32_t old_tlli) { - return bts->ms_store; + return bts_get_ms(bts, tlli, old_tlli, NULL); } -struct GprsMs *bts_ms_by_tlli(struct gprs_rlcmac_bts *bts, uint32_t tlli, uint32_t old_tlli) +struct GprsMs *bts_get_ms_by_imsi(const struct gprs_rlcmac_bts *bts, const char *imsi) { - return bts_ms_store(bts)->get_ms(tlli, old_tlli); + return bts_get_ms(bts, GSM_RESERVED_TMSI, GSM_RESERVED_TMSI, imsi); } /* update TA based on TA provided by PH-DATA-IND */ @@ -1175,7 +1294,7 @@ void bts_update_tbf_ta(struct gprs_rlcmac_bts *bts, const char *p, uint32_t fn, poll->tbf_poll.poll_tbf->direction != GPRS_RLCMAC_UL_TBF) goto no_tbf; - tbf = as_ul_tbf(poll->tbf_poll.poll_tbf); + tbf = tbf_as_ul_tbf(poll->tbf_poll.poll_tbf); /* we need to distinguish TA information provided by L1 * from PH-DATA-IND and PHY-RA-IND so that we can properly * update TA for given TBF @@ -1332,16 +1451,6 @@ void bts_recalc_max_mcs(struct gprs_rlcmac_bts *bts) bts_set_max_mcs_ul(bts, mcs_ul); } -struct GprsMs *bts_ms_by_imsi(struct gprs_rlcmac_bts *bts, const char *imsi) -{ - return bts_ms_store(bts)->get_ms(0, 0, imsi); -} - -const struct llist_head* bts_ms_list(struct gprs_rlcmac_bts *bts) -{ - return bts_ms_store(bts)->ms_list(); -} - uint8_t bts_get_ms_pwr_alpha(const struct gprs_rlcmac_bts *bts) { if (bts->pcu->vty.force_alpha != (uint8_t)-1) @@ -1352,3 +1461,20 @@ uint8_t bts_get_ms_pwr_alpha(const struct gprs_rlcmac_bts *bts) * B.2 Closed loop control */ return 0; } + +/* Used by counter availablePDCHAllocatedTime, TS 52.402 B.2.1.45 "All available PDCH allocated time" */ +bool bts_all_pdch_allocated(const struct gprs_rlcmac_bts *bts) +{ + unsigned trx_no, ts_no; + for (trx_no = 0; trx_no < ARRAY_SIZE(bts->trx); trx_no++) { + const struct gprs_rlcmac_trx *trx = &bts->trx[trx_no]; + for (ts_no = 0; ts_no < ARRAY_SIZE(trx->pdch); ts_no++) { + const struct gprs_rlcmac_pdch *pdch = &trx->pdch[ts_no]; + if (!pdch_is_enabled(pdch)) + continue; + if(!pdch_is_full(pdch)) + return false; + } + } + return true; +} @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -32,6 +28,7 @@ extern "C" { #include <osmocom/core/rate_ctr.h> #include <osmocom/core/stat_item.h> #include <osmocom/core/tdef.h> +#include <osmocom/core/time_cc.h> #include <osmocom/gprs/gprs_ns2.h> #include <osmocom/gsm/l1sap.h> #include <osmocom/gsm/protocol/gsm_04_08.h> @@ -47,6 +44,10 @@ extern "C" { #include "tbf.h" #include "coding_scheme.h" +#ifdef __cplusplus +extern "C" { +#endif + struct GprsMs; struct gprs_rlcmac_bts; @@ -67,9 +68,6 @@ struct gprs_rlcmac_trx { }; -#ifdef __cplusplus -extern "C" { -#endif void bts_trx_init(struct gprs_rlcmac_trx *trx, struct gprs_rlcmac_bts *bts, uint8_t trx_no); void bts_trx_reserve_slots(struct gprs_rlcmac_trx *trx, enum gprs_rlcmac_tbf_direction dir, uint8_t slots); void bts_trx_unreserve_slots(struct gprs_rlcmac_trx *trx, enum gprs_rlcmac_tbf_direction dir, uint8_t slots); @@ -77,13 +75,9 @@ void bts_trx_free_all_tbf(struct gprs_rlcmac_trx *trx); void bts_update_tbf_ta(struct gprs_rlcmac_bts *bts, const char *p, uint32_t fn, uint8_t trx_no, uint8_t ts, int8_t ta, bool is_rach); -#ifdef __cplusplus -} -#endif - - enum { + CTR_PDCH_ALL_ALLOCATED, CTR_TBF_DL_ALLOCATED, CTR_TBF_DL_FREED, CTR_TBF_DL_ABORTED, @@ -126,14 +120,21 @@ enum { CTR_LLC_DL_BYTES, CTR_LLC_UL_BYTES, CTR_PCH_REQUESTS, + CTR_PCH_REQUESTS_ALREADY, CTR_PCH_REQUESTS_TIMEDOUT, CTR_RACH_REQUESTS, - CTR_11BIT_RACH_REQUESTS, + CTR_RACH_REQUESTS_11BIT, + CTR_RACH_REQUESTS_ONE_PHASE, + CTR_RACH_REQUESTS_TWO_PHASE, + CTR_RACH_REQUESTS_UNEXPECTED, CTR_SPB_UL_FIRST_SEGMENT, CTR_SPB_UL_SECOND_SEGMENT, CTR_SPB_DL_FIRST_SEGMENT, CTR_SPB_DL_SECOND_SEGMENT, CTR_IMMEDIATE_ASSIGN_UL_TBF, + CTR_IMMEDIATE_ASSIGN_UL_TBF_ONE_PHASE, + CTR_IMMEDIATE_ASSIGN_UL_TBF_TWO_PHASE, + CTR_IMMEDIATE_ASSIGN_UL_TBF_CONTENTION_RESOLUTION_SUCCESS, CTR_IMMEDIATE_ASSIGN_REJ, CTR_IMMEDIATE_ASSIGN_DL_TBF, CTR_CHANNEL_REQUEST_DESCRIPTION, @@ -195,7 +196,8 @@ struct rach_ind_params { uint16_t ra; uint8_t trx_nr; uint8_t ts_nr; - uint32_t rfn; + uint16_t rfn; + uint32_t fn; int16_t qta; }; @@ -206,7 +208,6 @@ struct chan_req_params { bool single_block; }; -struct GprsMsStorage; struct pcu_l1_meas; /** @@ -267,15 +268,21 @@ struct gprs_rlcmac_bts { struct rate_ctr_group *ratectrs; struct osmo_stat_item_group *statg; - struct GprsMsStorage *ms_store; + struct llist_head ms_list; /* list of struct GprsMs */ /* List of struct bts_pch_timer for active PCH pagings */ struct llist_head pch_timer; -}; -#ifdef __cplusplus -extern "C" { -#endif + struct osmo_time_cc all_allocated_pdch; + + /* BTS hardware model, see pcuif_proto.h */ + uint8_t bts_model; + + /* When the PDCH is idle, the timeslot is expected to transmit dummy blocks. Some BTS models will generate + * those dummy blocks automatically when no data is transmitted. In contrast, other BTS models may require a + * continuous stream of PDCH blocks that already has the gaps filled with dummy blocks. */ + bool gen_idle_blocks_C0; +}; struct paging_req_cs { uint8_t chan_needed; @@ -286,15 +293,14 @@ struct paging_req_cs { struct osmo_mobile_identity mi_imsi; }; -struct GprsMs *bts_alloc_ms(struct gprs_rlcmac_bts *bts, uint8_t ms_class, uint8_t egprs_ms_class); int bts_add_paging(struct gprs_rlcmac_bts *bts, const struct paging_req_cs *req, struct GprsMs *ms); -uint32_t bts_rfn_to_fn(const struct gprs_rlcmac_bts *bts, int32_t rfn); +uint32_t bts_rfn_to_fn(const struct gprs_rlcmac_bts *bts, uint32_t rfn); struct gprs_rlcmac_dl_tbf *bts_dl_tbf_by_tfi(struct gprs_rlcmac_bts *bts, uint8_t tfi, uint8_t trx, uint8_t ts); struct gprs_rlcmac_ul_tbf *bts_ul_tbf_by_tfi(struct gprs_rlcmac_bts *bts, uint8_t tfi, uint8_t trx, uint8_t ts); -void bts_snd_dl_ass(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, uint16_t pgroup); +void bts_snd_dl_ass(struct gprs_rlcmac_bts *bts, const struct gprs_rlcmac_dl_tbf *tbf); void bts_set_current_frame_number(struct gprs_rlcmac_bts *bts, uint32_t frame_number); void bts_set_current_block_frame_number(struct gprs_rlcmac_bts *bts, int frame_number); @@ -308,7 +314,7 @@ int bts_tfi_find_free(const struct gprs_rlcmac_bts *bts, enum gprs_rlcmac_tbf_di int bts_rcv_rach(struct gprs_rlcmac_bts *bts, const struct rach_ind_params *rip); int bts_rcv_ptcch_rach(struct gprs_rlcmac_bts *bts, const struct rach_ind_params *rip); -int bts_rcv_imm_ass_cnf(struct gprs_rlcmac_bts *bts, const uint8_t *data, uint32_t fn); +int bts_rcv_imm_ass_cnf(struct gprs_rlcmac_bts *bts, const uint8_t *data, uint32_t tlli); void bts_send_gsmtap(struct gprs_rlcmac_bts *bts, enum pcu_gsmtap_category categ, bool uplink, uint8_t trx_no, @@ -322,9 +328,10 @@ void bts_send_gsmtap_rach(struct gprs_rlcmac_bts *bts, enum pcu_gsmtap_category categ, uint8_t channel, const struct rach_ind_params *rip); -struct GprsMsStorage *bts_ms_store(const struct gprs_rlcmac_bts *bts); - -struct GprsMs *bts_ms_by_tlli(struct gprs_rlcmac_bts *bts, uint32_t tlli, uint32_t old_tlli); +struct GprsMs *bts_get_ms(const struct gprs_rlcmac_bts *bts, uint32_t tlli, uint32_t old_tlli, + const char *imsi); +struct GprsMs *bts_get_ms_by_tlli(const struct gprs_rlcmac_bts *bts, uint32_t tlli, uint32_t old_tlli); +struct GprsMs *bts_get_ms_by_imsi(const struct gprs_rlcmac_bts *bts, const char *imsi); static inline struct rate_ctr_group *bts_rate_counters(struct gprs_rlcmac_bts *bts) { @@ -362,7 +369,6 @@ void bts_recalc_initial_cs(struct gprs_rlcmac_bts *bts); void bts_recalc_initial_mcs(struct gprs_rlcmac_bts *bts); void bts_recalc_max_cs(struct gprs_rlcmac_bts *bts); void bts_recalc_max_mcs(struct gprs_rlcmac_bts *bts); -struct GprsMs *bts_ms_by_imsi(struct gprs_rlcmac_bts *bts, const char *imsi); uint8_t bts_max_cs_dl(const struct gprs_rlcmac_bts *bts); uint8_t bts_max_cs_ul(const struct gprs_rlcmac_bts *bts); uint8_t bts_max_mcs_dl(const struct gprs_rlcmac_bts *bts); @@ -372,8 +378,8 @@ void bts_set_max_cs_ul(struct gprs_rlcmac_bts *bts, uint8_t cs_ul); void bts_set_max_mcs_dl(struct gprs_rlcmac_bts *bts, uint8_t mcs_dl); void bts_set_max_mcs_ul(struct gprs_rlcmac_bts *bts, uint8_t mcs_ul); bool bts_cs_dl_is_supported(const struct gprs_rlcmac_bts *bts, enum CodingScheme cs); -const struct llist_head* bts_ms_list(struct gprs_rlcmac_bts *bts); uint8_t bts_get_ms_pwr_alpha(const struct gprs_rlcmac_bts *bts); +bool bts_all_pdch_allocated(const struct gprs_rlcmac_bts *bts); #ifdef __cplusplus } #endif diff --git a/src/bts_pch_timer.c b/src/bts_pch_timer.c index 20373ac8..4b752398 100644 --- a/src/bts_pch_timer.c +++ b/src/bts_pch_timer.c @@ -3,8 +3,8 @@ * Author: Oliver Smith * * 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 + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, @@ -12,7 +12,7 @@ * 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 + * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ @@ -26,8 +26,22 @@ #include <gprs_debug.h> #include <gprs_pcu.h> #include <bts_pch_timer.h> +#include <gprs_ms.h> -static struct bts_pch_timer *bts_pch_timer_get(struct gprs_rlcmac_bts *bts, const char *imsi) +static struct bts_pch_timer *bts_pch_timer_get_by_ptmsi(struct gprs_rlcmac_bts *bts, uint32_t ptmsi) +{ + struct bts_pch_timer *p; + OSMO_ASSERT(ptmsi != GSM_RESERVED_TMSI); + + llist_for_each_entry(p, &bts->pch_timer, entry) { + if (p->ptmsi != GSM_RESERVED_TMSI && p->ptmsi == ptmsi) + return p; + } + + return NULL; +} + +struct bts_pch_timer *bts_pch_timer_get_by_imsi(struct gprs_rlcmac_bts *bts, const char *imsi) { struct bts_pch_timer *p; @@ -57,29 +71,42 @@ static void T3113_callback(void *data) bts_pch_timer_remove(p); } -void bts_pch_timer_start(struct gprs_rlcmac_bts *bts, const char *imsi) +void bts_pch_timer_start(struct gprs_rlcmac_bts *bts, const struct osmo_mobile_identity *mi_paging, + const char *imsi) { - if (bts_pch_timer_get(bts, imsi)) - return; - struct bts_pch_timer *p; + struct osmo_tdef *tdef; + p = talloc_zero(bts, struct bts_pch_timer); llist_add_tail(&p->entry, &bts->pch_timer); - osmo_strlcpy(p->imsi, imsi, sizeof(p->imsi)); p->bts = bts; + OSMO_STRLCPY_ARRAY(p->imsi, imsi); + p->ptmsi = (mi_paging->type == GSM_MI_TYPE_TMSI) ? mi_paging->tmsi : GSM_RESERVED_TMSI; - struct osmo_tdef *tdef = osmo_tdef_get_entry(the_pcu->T_defs, 3113); + tdef = osmo_tdef_get_entry(the_pcu->T_defs, 3113); OSMO_ASSERT(tdef); osmo_timer_setup(&p->T3113, T3113_callback, p); osmo_timer_schedule(&p->T3113, tdef->val, 0); - LOGP(DPCU, LOGL_DEBUG, "PCH paging timer started for IMSI=%s\n", p->imsi); + if (log_check_level(DPCU, LOGL_DEBUG)) { + char str[64]; + osmo_mobile_identity_to_str_buf(str, sizeof(str), mi_paging); + LOGP(DPCU, LOGL_DEBUG, "PCH paging timer started for MI=%s IMSI=%s\n", str, p->imsi); + } } -void bts_pch_timer_stop(struct gprs_rlcmac_bts *bts, const char *imsi) +void bts_pch_timer_stop(struct gprs_rlcmac_bts *bts, const struct GprsMs *ms) { - struct bts_pch_timer *p = bts_pch_timer_get(bts, imsi); - + struct bts_pch_timer *p = NULL; + uint32_t tlli = ms_tlli(ms); + const char *imsi = ms_imsi(ms); + + /* First try matching by TMSI if available in MS */ + if (tlli != GSM_RESERVED_TMSI) + p = bts_pch_timer_get_by_ptmsi(bts, tlli); + /* Otherwise try matching by IMSI if available in MS */ + if (!p && imsi[0] != '\0') + p = bts_pch_timer_get_by_imsi(bts, imsi); if (p) bts_pch_timer_remove(p); } diff --git a/src/bts_pch_timer.h b/src/bts_pch_timer.h index 26b89c80..c65f9fa2 100644 --- a/src/bts_pch_timer.h +++ b/src/bts_pch_timer.h @@ -12,7 +12,7 @@ * 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 + * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ @@ -32,12 +32,17 @@ struct bts_pch_timer { struct llist_head entry; struct gprs_rlcmac_bts *bts; struct osmo_timer_list T3113; + uint32_t ptmsi; /* GSM_RESERVED_TMSI if not available */ char imsi[OSMO_IMSI_BUF_SIZE]; }; -void bts_pch_timer_start(struct gprs_rlcmac_bts *bts, const char *imsi); -void bts_pch_timer_stop(struct gprs_rlcmac_bts *bts, const char *imsi); +struct GprsMs; + +void bts_pch_timer_start(struct gprs_rlcmac_bts *bts, const struct osmo_mobile_identity *mi_paging, + const char *imsi); +void bts_pch_timer_stop(struct gprs_rlcmac_bts *bts, const struct GprsMs *ms); void bts_pch_timer_stop_all(struct gprs_rlcmac_bts *bts); +struct bts_pch_timer *bts_pch_timer_get_by_imsi(struct gprs_rlcmac_bts *bts, const char *imsi); #ifdef __cplusplus } diff --git a/src/coding_scheme.c b/src/coding_scheme.c index 27241d19..c370c4b6 100644 --- a/src/coding_scheme.c +++ b/src/coding_scheme.c @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <stdint.h> diff --git a/src/coding_scheme.h b/src/coding_scheme.h index 1b212650..fdecf1c9 100644 --- a/src/coding_scheme.h +++ b/src/coding_scheme.h @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -22,10 +22,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <assert.h> @@ -19,10 +19,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _PACKET_CSN1_H_ diff --git a/src/csn1_dec.c b/src/csn1_dec.c index fa1f0c39..f0d07ac7 100644 --- a/src/csn1_dec.c +++ b/src/csn1_dec.c @@ -22,10 +22,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <assert.h> @@ -996,7 +992,7 @@ csnStreamDecoder(csnStream_t* ar, const CSN_DESCR* pDescr, struct bitvec *vector } } LOGPC(DCSN1, LOGL_DEBUG, "%s = %u%s | ", pDescr->sz , (unsigned)fExist, fExist && isnull ? " (NULL)" : ""); - *pui8++ = fExist; + *pui8++ = !isnull; remaining_bits_len -= 1; bit_offset++; diff --git a/src/csn1_enc.c b/src/csn1_enc.c index 5518d063..e4e5e092 100644 --- a/src/csn1_enc.c +++ b/src/csn1_enc.c @@ -22,10 +22,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <assert.h> diff --git a/src/cxx_linuxlist.h b/src/cxx_linuxlist.h index 9a74b3fe..1562bfe1 100644 --- a/src/cxx_linuxlist.h +++ b/src/cxx_linuxlist.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once diff --git a/src/decoding.cpp b/src/decoding.cpp index 0caa8bfd..9f93f015 100644 --- a/src/decoding.cpp +++ b/src/decoding.cpp @@ -12,20 +12,17 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <decoding.h> #include <rlc.h> +#include <rlc_window.h> #include <gprs_debug.h> #include <egprs_rlc_compression.h> extern "C" { #include <osmocom/core/utils.h> #include <osmocom/core/bitcomp.h> -#include <osmocom/gprs/protocol/gsm_04_60.h> +#include <osmocom/gsm/protocol/gsm_44_060.h> } #include <arpa/inet.h> diff --git a/src/decoding.h b/src/decoding.h index 7270c3d8..c89b5b1a 100644 --- a/src/decoding.h +++ b/src/decoding.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -36,6 +32,8 @@ struct bitvec; #ifdef __cplusplus +#include "rlc_window_dl.h" + class Decoding { public: /* represents (parts) LLC PDUs within one RLC Data block */ diff --git a/src/encoding.cpp b/src/encoding.cpp index 30b0ee1d..56956665 100644 --- a/src/encoding.cpp +++ b/src/encoding.cpp @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <encoding.h> @@ -29,7 +25,7 @@ #include <egprs_rlc_compression.h> extern "C" { -#include <osmocom/gprs/protocol/gsm_04_60.h> +#include <osmocom/gsm/protocol/gsm_44_060.h> #include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/gsm/gsm48.h> } @@ -67,22 +63,22 @@ static int write_alpha_gamma(bitvec *dest, uint8_t alpha, uint8_t gamma) } /* TBF_STARTING_TIME -- same as 3GPP TS 44.018 §10.5.2.38 Starting Time without tag: */ -static int write_tbf_start_time(bitvec *dest, uint32_t fn) +static int write_tbf_start_time(bitvec *dest, uint16_t rfn) { int rc; /* Set values according to 3GPP TS 44.018 Table 10.5.2.38.1 */ /* T1' */ - rc = bitvec_set_u64(dest, (fn / (26 * 51)) % 32, 5, false); + rc = bitvec_set_u64(dest, (rfn / (26 * 51)) % 32, 5, false); CHECK(rc); /* T3 */ - rc = bitvec_set_u64(dest, fn % 51, 6, false); + rc = bitvec_set_u64(dest, rfn % 51, 6, false); CHECK(rc); /* T2 */ - rc = bitvec_set_u64(dest, fn % 26, 5, false); + rc = bitvec_set_u64(dest, rfn % 26, 5, false); CHECK(rc); return rc; @@ -184,7 +180,7 @@ static inline void write_ta_ie(bitvec *dest, unsigned& wp, } static int write_ia_rest_downlink(const gprs_rlcmac_dl_tbf *tbf, bitvec * dest, bool polling, bool ta_valid, - uint32_t fn, uint8_t alpha, uint8_t gamma, int8_t ta_idx) + uint16_t rfn, uint8_t alpha, uint8_t gamma, int8_t ta_idx) { int rc = 0; @@ -217,7 +213,7 @@ static int write_ia_rest_downlink(const gprs_rlcmac_dl_tbf *tbf, bitvec * dest, if (polling) { SET_1(dest); - rc = write_tbf_start_time(dest, fn); + rc = write_tbf_start_time(dest, rfn); CHECK(rc); } else SET_0(dest); @@ -239,7 +235,7 @@ static int write_ia_rest_downlink(const gprs_rlcmac_dl_tbf *tbf, bitvec * dest, } /* 3GPP TS 44.018 Table 10.5.2.16.1 < Packet Uplink Assignment > -- Single Block Allocation */ -static int write_ia_rest_uplink_sba(bitvec *dest, uint32_t fn, uint8_t alpha, uint8_t gamma) +static int write_ia_rest_uplink_sba(bitvec *dest, uint32_t rfn, uint8_t alpha, uint8_t gamma) { int rc = 0; @@ -252,7 +248,7 @@ static int write_ia_rest_uplink_sba(bitvec *dest, uint32_t fn, uint8_t alpha, ui SET_0(dest); SET_1(dest); - rc = write_tbf_start_time(dest, fn); + rc = write_tbf_start_time(dest, rfn); CHECK(rc); /* No P0 nor PR_MODE */ @@ -296,7 +292,7 @@ static int write_ia_rest_uplink_mba(const gprs_rlcmac_ul_tbf *tbf, bitvec *dest, return rc; } -static int write_ia_rest_egprs_uplink_mba(bitvec * dest, uint32_t fn, uint8_t alpha, uint8_t gamma) +static int write_ia_rest_egprs_uplink_mba(bitvec * dest, uint32_t rfn, uint8_t alpha, uint8_t gamma) { int rc = 0; @@ -305,7 +301,7 @@ static int write_ia_rest_egprs_uplink_mba(bitvec * dest, uint32_t fn, uint8_t al rc = write_alpha_gamma(dest, alpha, gamma); CHECK(rc); - rc = write_tbf_start_time(dest, fn); + rc = write_tbf_start_time(dest, rfn); CHECK(rc); SET_0(dest); /* NUMBER OF RADIO BLOCKS ALLOCATED: */ @@ -362,7 +358,7 @@ static int write_ia_rest_egprs_uplink_sba(const gprs_rlcmac_ul_tbf *tbf, bitvec * see GSM 44.018, 9.1.20 + 10.5.2.30 */ int Encoding::write_immediate_assignment_reject(bitvec *dest, uint16_t ra, - uint32_t ref_fn, enum ph_burst_type burst_type, uint8_t t3142) + uint16_t ref_rfn, enum ph_burst_type burst_type, uint8_t t3142) { unsigned wp = 0; int plen; @@ -398,9 +394,9 @@ int Encoding::write_immediate_assignment_reject(bitvec *dest, uint16_t ra, } bitvec_write_field(dest, &wp, - (ref_fn / (26 * 51)) % 32, 5); // T1' - bitvec_write_field(dest, &wp, ref_fn % 51, 6); // T3 - bitvec_write_field(dest, &wp, ref_fn % 26, 5); // T2 + (ref_rfn / (26 * 51)) % 32, 5); // T1' + bitvec_write_field(dest, &wp, ref_rfn % 51, 6); // T3 + bitvec_write_field(dest, &wp, ref_rfn % 26, 5); // T2 /* 10.5.2.43 Wait Indication */ bitvec_write_field(dest, &wp, t3142, 8); @@ -439,27 +435,27 @@ int Encoding::write_immediate_assignment_reject(bitvec *dest, uint16_t ra, */ int Encoding::write_immediate_assignment( const struct gprs_rlcmac_pdch *pdch, - struct gprs_rlcmac_tbf *tbf, + const struct gprs_rlcmac_tbf *tbf, bitvec * dest, bool downlink, uint16_t ra, - uint32_t ref_fn, uint8_t ta, - uint8_t usf, bool polling, uint32_t fn, uint8_t alpha, + uint16_t ref_rfn, uint8_t ta, + uint8_t usf, bool polling, uint16_t rfn, uint8_t alpha, uint8_t gamma, int8_t ta_idx, enum ph_burst_type burst_type) { unsigned wp = 0; int plen; int rc; - bitvec_write_field(dest, &wp,0x0,4); // Skip Indicator - bitvec_write_field(dest, &wp,0x6,4); // Protocol Discriminator - bitvec_write_field(dest, &wp,0x3F,8); // Immediate Assignment Message Type + bitvec_write_field(dest, &wp, 0x0, 4); // Skip Indicator + bitvec_write_field(dest, &wp, 0x6, 4); // Protocol Discriminator + bitvec_write_field(dest, &wp, GSM48_MT_RR_IMM_ASS, 8); // Immediate Assignment Message Type // 10.5.2.25b Dedicated mode or TBF - bitvec_write_field(dest, &wp,0x0,1); // spare - bitvec_write_field(dest, &wp,0x0,1); // TMA : Two-message assignment: No meaning - bitvec_write_field(dest, &wp,downlink,1); // Downlink : Downlink assignment to mobile in packet idle mode - bitvec_write_field(dest, &wp,0x1,1); // T/D : TBF or dedicated mode: this message assigns a Temporary Block Flow (TBF). + bitvec_write_field(dest, &wp, 0x0, 1); // spare + bitvec_write_field(dest, &wp, 0x0, 1); // TMA : Two-message assignment: No meaning + bitvec_write_field(dest, &wp, downlink, 1); // Downlink : Downlink assignment to mobile in packet idle mode + bitvec_write_field(dest, &wp, 0x1, 1); // T/D : TBF or dedicated mode: this message assigns a Temporary Block Flow (TBF). - bitvec_write_field(dest, &wp,0x0,4); // Page Mode + bitvec_write_field(dest, &wp, 0x0, 4); // Page Mode // GSM 04.08 10.5.2.25a Packet Channel Description bitvec_write_field(dest, &wp, 0x01, 5); // Channel type @@ -484,13 +480,13 @@ int Encoding::write_immediate_assignment( bitvec_write_field(dest, &wp, ra, 8); /* RACH value */ } - bitvec_write_field(dest, &wp,(ref_fn / (26 * 51)) % 32,5); // T1' - bitvec_write_field(dest, &wp,ref_fn % 51,6); // T3 - bitvec_write_field(dest, &wp,ref_fn % 26,5); // T2 + bitvec_write_field(dest, &wp, (ref_rfn / (26 * 51)) % 32, 5); // T1' + bitvec_write_field(dest, &wp, ref_rfn % 51, 6); // T3 + bitvec_write_field(dest, &wp, ref_rfn % 26, 5); // T2 // 10.5.2.40 Timing Advance - bitvec_write_field(dest, &wp,0x0,2); // spare - bitvec_write_field(dest, &wp,ta,6); // Timing Advance value + bitvec_write_field(dest, &wp, 0x0, 2); // spare + bitvec_write_field(dest, &wp, ta, 6); // Timing Advance value /* 10.5.2.21 Mobile Allocation */ if (pdch->fh.enabled) { @@ -510,9 +506,9 @@ int Encoding::write_immediate_assignment( /* 3GPP TS 44.018 §10.5.2.16 IA Rest Octets */ dest->cur_bit = wp; if (downlink) { - OSMO_ASSERT(as_dl_tbf(tbf) != NULL); + OSMO_ASSERT(tbf_as_dl_tbf_const(tbf) != NULL); - rc = write_ia_rest_downlink(as_dl_tbf(tbf), dest, polling, gsm48_ta_is_valid(ta), fn, alpha, gamma, + rc = write_ia_rest_downlink(tbf_as_dl_tbf_const(tbf), dest, polling, gsm48_ta_is_valid(ta), rfn, alpha, gamma, ta_idx); } else if (((burst_type == GSM_L1_BURST_TYPE_ACCESS_1) || (burst_type == GSM_L1_BURST_TYPE_ACCESS_2))) { SET_L(dest); SET_H(dest); // "LH" @@ -522,20 +518,20 @@ int Encoding::write_immediate_assignment( SET_0(dest); // No < Access Technologies Request struct > - if (as_ul_tbf(tbf) != NULL) - rc = write_ia_rest_egprs_uplink_sba(as_ul_tbf(tbf), dest, usf, alpha, gamma, ta_idx); + if (tbf_as_ul_tbf_const(tbf) != NULL) + rc = write_ia_rest_egprs_uplink_sba(tbf_as_ul_tbf_const(tbf), dest, usf, alpha, gamma, ta_idx); else - rc = write_ia_rest_egprs_uplink_mba(dest, fn, alpha, gamma); + rc = write_ia_rest_egprs_uplink_mba(dest, rfn, alpha, gamma); } else { OSMO_ASSERT(!tbf || !tbf->is_egprs_enabled()); SET_H(dest); SET_H(dest); // "HH" SET_0(dest); SET_0(dest); // "00" < Packet Uplink Assignment > - if (as_ul_tbf(tbf) != NULL) - rc = write_ia_rest_uplink_mba(as_ul_tbf(tbf), dest, usf, alpha, gamma, ta_idx); + if (tbf_as_ul_tbf_const(tbf) != NULL) + rc = write_ia_rest_uplink_mba(tbf_as_ul_tbf_const(tbf), dest, usf, alpha, gamma, ta_idx); else - rc = write_ia_rest_uplink_sba(dest, fn, alpha, gamma); + rc = write_ia_rest_uplink_sba(dest, rfn, alpha, gamma); } if (rc < 0) { @@ -557,7 +553,7 @@ static void gen_freq_params(Frequency_Parameters_t *freq_params, Direct_encoding_1_t fh_params; /* Check one PDCH, if it's hopping then all other should too */ - pdch = tbf->pdch[tbf->first_ts]; + pdch = tbf_get_first_ts_const(tbf); OSMO_ASSERT(pdch != NULL); /* Training Sequence Code */ @@ -566,7 +562,7 @@ static void gen_freq_params(Frequency_Parameters_t *freq_params, /* If frequency hopping is not in use, encode a single ARFCN */ if (!pdch->fh.enabled) { freq_params->UnionType = 0x00; - freq_params->u.ARFCN = tbf->trx->arfcn; + freq_params->u.ARFCN = pdch->trx->arfcn; return; } @@ -577,6 +573,8 @@ static void gen_freq_params(Frequency_Parameters_t *freq_params, fh_params.MAIO = pdch->fh.maio; fh_params.GPRS_Mobile_Allocation.HSN = pdch->fh.hsn; fh_params.GPRS_Mobile_Allocation.ElementsOf_RFL_NUMBER = 0; + memset(&fh_params.GPRS_Mobile_Allocation.RFL_NUMBER[0], 0x00, + sizeof(fh_params.GPRS_Mobile_Allocation.RFL_NUMBER)); /* Mobile Allocation bitmap */ fh_params.GPRS_Mobile_Allocation.UnionType = 0; /* MA bitmap */ @@ -607,7 +605,7 @@ void write_packet_uplink_assignment(RlcMacDownlink_t *block, uint8_t old_tfi, /* See 3GPP TS 44.060, section 11.2.29 */ pua = &block->u.Packet_Uplink_Assignment; - pua->MESSAGE_TYPE = 0x0a; // Packet Uplink Assignment + pua->MESSAGE_TYPE = MT_PACKET_UPLINK_ASSIGNMENT; // Packet Uplink Assignment pua->PAGE_MODE = 0x00; // Normal Paging /* TLLI or Global (UL/DL) TFI */ @@ -803,7 +801,7 @@ void write_packet_downlink_assignment(RlcMacDownlink_t * block, } /* Generate paging request. See 44.018, sections 10 and 9.1.22 */ -int Encoding::write_paging_request(bitvec * dest, const struct osmo_mobile_identity *mi) +int write_paging_request(struct bitvec *dest, const struct osmo_mobile_identity *mi) { uint8_t mi_buf[GSM48_MID_MAX_SIZE]; int mi_len; diff --git a/src/encoding.h b/src/encoding.h index 998e69aa..89f69640 100644 --- a/src/encoding.h +++ b/src/encoding.h @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -48,25 +44,23 @@ class Encoding { public: static int write_immediate_assignment( const struct gprs_rlcmac_pdch *pdch, - struct gprs_rlcmac_tbf *tbf, + const struct gprs_rlcmac_tbf *tbf, bitvec * dest, bool downlink, uint16_t ra, - uint32_t ref_fn, uint8_t ta, + uint16_t ref_rfn, uint8_t ta, uint8_t usf, bool polling, - uint32_t fn, uint8_t alpha, uint8_t gamma, + uint16_t rfn, uint8_t alpha, uint8_t gamma, int8_t ta_idx, enum ph_burst_type burst_type); static int write_immediate_assignment_reject( bitvec *dest, uint16_t ra, - uint32_t ref_fn, + uint16_t ref_rfn, enum ph_burst_type burst_type, uint8_t t3142 ); static void encode_rbb(const char *show_rbb, bitvec *rbb); - static int write_paging_request(bitvec * dest, const struct osmo_mobile_identity *mi); - static unsigned write_repeated_page_info(bitvec * dest, unsigned& wp, uint8_t len, uint8_t *identity, uint8_t chan_needed); @@ -102,6 +96,9 @@ extern "C" { #endif void write_packet_access_reject(struct bitvec *dest, uint32_t tlli, unsigned long t3172_ms); + +int write_paging_request(struct bitvec *dest, const struct osmo_mobile_identity *mi); + void write_packet_uplink_assignment(RlcMacDownlink_t *block, uint8_t old_tfi, uint8_t old_downlink, uint32_t tlli, uint8_t use_tlli, const struct gprs_rlcmac_ul_tbf *tbf, uint8_t poll, diff --git a/src/ericsson-rbs/er_ccu_descr.h b/src/ericsson-rbs/er_ccu_descr.h new file mode 100644 index 00000000..9fe1aab8 --- /dev/null +++ b/src/ericsson-rbs/er_ccu_descr.h @@ -0,0 +1,53 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/trau/trau_pcu_ericsson.h> + +struct er_ccu_descr; +struct e1_conn_pars; +typedef void (er_ccu_empty) (struct er_ccu_descr *ccu_descr); +typedef void (er_ccu_rx) (struct er_ccu_descr *ccu_descr, const ubit_t *bits, unsigned int num_bits); + +struct er_ccu_descr { + + /* E1-line and timeslot (filled in by user) */ + struct e1_conn_pars *e1_conn_pars; + + /* Callback functions (provided by user) */ + er_ccu_empty *er_ccu_empty_cb; + er_ccu_rx *er_ccu_rx_cb; + + /* I.460 Subslot */ + struct { + struct osmo_i460_schan_desc scd; + struct osmo_i460_subchan *schan; + struct osmo_fsm_inst *trau_sync_fi; + bool ccu_connected; + } link; + + /* TRAU Sync state */ + struct { + uint32_t pseq_ccu; /* CCU sequence counter (remote) */ + uint32_t pseq_pcu; /* PCU sequence counter (local) */ + uint32_t last_afn_ul; /* Adjusted frame number, uplink */ + uint32_t last_afn_dl; /* Adjusted frame number, downlink */ + enum time_adj_val tav; /* Last time adjustment value */ + bool ul_frame_err; /* True when last uplink TRAU frame was bad */ + bool ccu_synced; /* True when PCU is in sync with CCU */ + } sync; + + /* PCU related context */ + struct { + uint8_t trx_no; + uint8_t bts_nr; + uint8_t ts; + } pcu; + + +}; + +struct er_trx_descr { + struct er_ccu_descr ts_ccu_descr[8]; +}; diff --git a/src/ericsson-rbs/er_ccu_if.c b/src/ericsson-rbs/er_ccu_if.c new file mode 100644 index 00000000..98abbf7d --- /dev/null +++ b/src/ericsson-rbs/er_ccu_if.c @@ -0,0 +1,416 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <er_ccu_if.h> +#include <er_ccu_descr.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/abis.h> +#include <osmocom/trau/trau_sync.h> +#include <osmocom/trau/trau_pcu_ericsson.h> +#include <bts.h> +#include <gprs_debug.h> +#include <pcu_l1_if.h> + +#define E1_TS_BYTES 160 +#define DEBUG_BITS_MAX 1280 +#define DEBUG_BYTES_MAX 40 + +#define LOGPCCU(ccu_descr, level, tag, fmt, args...) \ + LOGP(DE1, level, "E1TS(%u:%u:%u) %s:" fmt, \ + ccu_descr->e1_conn_pars->e1_nr, ccu_descr->e1_conn_pars->e1_ts, \ + ccu_descr->e1_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL ? 0 : ccu_descr->e1_conn_pars->e1_ts_ss, tag, \ + ## args) + +struct e1_ts_descr { + uint8_t usecount; + bool i460_ts_initialized; + struct osmo_i460_timeslot i460_ts; +}; + +struct e1_line_descr { + struct e1_ts_descr e1_ts[NUM_E1_TS - 1]; +}; + +static struct e1_line_descr e1_lines[32]; +static void *tall_ccu_ctx = NULL; + +static const struct e1inp_line_ops dummy_e1_line_ops = { + .sign_link_up = NULL, + .sign_link_down = NULL, + .sign_link = NULL, +}; + +/* called by trau frame synchronizer: feed received MAC blocks into PCU */ +static void sync_frame_out_cb(void *user_data, const ubit_t *bits, unsigned int num_bits) +{ + struct er_ccu_descr *ccu_descr = user_data; + + if (!bits || num_bits == 0) + return; + + LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-RX", "receiving %u TRAU frame bits from subslot (synchronized): %s...\n", + num_bits, osmo_ubit_dump(bits, num_bits > DEBUG_BITS_MAX ? DEBUG_BITS_MAX : num_bits)); + + ccu_descr->er_ccu_rx_cb(ccu_descr, bits, num_bits); +} + +/* called by I.460 de-multiplexer: feed output of I.460 demux into TRAU frame sync */ +static void e1_i460_demux_bits_cb(struct osmo_i460_subchan *schan, void *user_data, const ubit_t *bits, + unsigned int num_bits) +{ + struct er_ccu_descr *ccu_descr = user_data; + + LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-RX", "receiving %u TRAU frame bits from subslot: %s...\n", num_bits, + osmo_ubit_dump(bits, num_bits > DEBUG_BITS_MAX ? DEBUG_BITS_MAX : num_bits)); + + OSMO_ASSERT(ccu_descr->link.trau_sync_fi); + osmo_trau_sync_rx_ubits(ccu_descr->link.trau_sync_fi, bits, num_bits); + +} + +/* called by I.460 de-multiplexer: ensure that sync indications are sent when mux buffer runs empty */ +static void e1_i460_mux_empty_cb(struct osmo_i460_subchan *schan2, void *user_data) +{ + struct er_ccu_descr *ccu_descr = user_data; + + LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-TX", "demux buffer empty\n"); + ccu_descr->er_ccu_empty_cb(ccu_descr); +} + +/* handle outgoing E1 traffic */ +static void e1_send_ts_frame(struct e1inp_ts *ts) +{ + void *ctx = tall_ccu_ctx; + struct e1_ts_descr *ts_descr; + struct msgb *msg; + uint8_t *ptr; + + /* The line number and ts number that arrives here should be clean. */ + OSMO_ASSERT(ts->line->num < ARRAY_SIZE(e1_lines)); + + ts_descr = &e1_lines[ts->line->num].e1_ts[ts->num]; + + /* Do not send anything in case the E1 timeslot is not ready. */ + if (ts_descr->usecount == 0) + return; + + /* Get E1 frame from I.460 multiplexer */ + msg = msgb_alloc_c(ctx, E1_TS_BYTES, "E1-TX-timeslot-bytes"); + ptr = msgb_put(msg, E1_TS_BYTES); + osmo_i460_mux_out(&ts_descr->i460_ts, ptr, E1_TS_BYTES); + + LOGPITS(ts, DE1, LOGL_DEBUG, "E1-TX: sending %u bytes: %s...\n", + msgb_length(msg), osmo_hexdump_nospc(msgb_data(msg), + msgb_length(msg) > + DEBUG_BYTES_MAX ? DEBUG_BYTES_MAX : msgb_length(msg))); + + /* Hand data over to the E1 stack */ + msgb_enqueue(&ts->raw.tx_queue, msg); +} + +/* Callback function to handle incoming E1 traffic */ +static void e1_recv_cb(struct e1inp_ts *ts, struct msgb *msg) +{ + struct e1_ts_descr *ts_descr; + + if (msg->len != E1_TS_BYTES) { + LOGPITS(ts, DE1, LOGL_ERROR, + "E1-RX: receiving bad, expected length is %u, actual length is %u!\n", + E1_TS_BYTES, msg->len); + msgb_free(msg); + return; + } + + LOGPITS(ts, DE1, LOGL_DEBUG, "E1-RX: receiving %u bytes: %s ...\n", + msg->len, osmo_hexdump_nospc(msg->data, msg->len)); + + /* Note: The line number and ts number that arrives here should be clean. */ + OSMO_ASSERT(ts->line->num < ARRAY_SIZE(e1_lines)); + ts_descr = &e1_lines[ts->line->num].e1_ts[ts->num]; + + /* Hand data over to the I640 demultiplexer. */ + osmo_i460_demux_in(&ts_descr->i460_ts, msg->data, msg->len); + + /* Trigger sending of pending E1 traffic */ + e1_send_ts_frame(ts); + + /* e1inp_rx_ts(), the caller of this callback does not free() msgb. */ + msgb_free(msg); +} + +static struct e1_ts_descr *ts_descr_from_ccu_descr(struct er_ccu_descr *ccu_descr) +{ + /* Make sure E1 line number is valid */ + if (ccu_descr->e1_conn_pars->e1_nr >= ARRAY_SIZE(e1_lines)) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "Invalid E1 line number!\n"); + return NULL; + } + + /* Make sure E1 timeslot number is valid */ + if (ccu_descr->e1_conn_pars->e1_ts < 1 || ccu_descr->e1_conn_pars->e1_ts > NUM_E1_TS - 1) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "Invalid E1 timeslot number!\n"); + return NULL; + } + + /* Timeslots are only initialized once and will stay open after that. */ + return &e1_lines[ccu_descr->e1_conn_pars->e1_nr].e1_ts[ccu_descr->e1_conn_pars->e1_ts]; +} + +/* Configure an I.460 subslot and add it to the CCU descriptor */ +static int add_i460_subslot(void *ctx, struct er_ccu_descr *ccu_descr) +{ + struct e1_ts_descr *ts_descr; + enum osmo_tray_sync_pat_id sync_pattern; + + if (ccu_descr->link.schan) { + /* NOTE: This is a serious error: subslots should be removed when l1if_close_trx() is called by the + * PCU. This log line points towards a problem with the PDCH management inside the PCU! */ + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "I.460 subslot is already configured -- will not touch it!\n"); + return -EINVAL; + } + + ts_descr = ts_descr_from_ccu_descr(ccu_descr); + if (!ts_descr) + return -EINVAL; + if (ts_descr->usecount == 0) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "E1 timeslot not ready!\n"); + return -EINVAL; + } + + /* Set up I.460 subchannel and connect it to the MUX on the E1 timeslot */ + if (ccu_descr->e1_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL) { + LOGPCCU(ccu_descr, LOGL_INFO, "SETUP", "using 64k subslots\n"); + ccu_descr->link.scd.rate = OSMO_I460_RATE_64k; + ccu_descr->link.scd.demux.num_bits = E1_TS_BYTES * 8; + ccu_descr->link.scd.bit_offset = 0; + sync_pattern = OSMO_TRAU_SYNCP_64_ER_CCU; + } else { + LOGPCCU(ccu_descr, LOGL_INFO, "SETUP", "using 16k subslots\n"); + if (ccu_descr->e1_conn_pars->e1_ts_ss > 3) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "Invalid I.460 subslot number!\n"); + return -EINVAL; + } + ccu_descr->link.scd.rate = OSMO_I460_RATE_16k; + ccu_descr->link.scd.demux.num_bits = E1_TS_BYTES / 4 * 8; + ccu_descr->link.scd.bit_offset = ccu_descr->e1_conn_pars->e1_ts_ss * 2; + sync_pattern = OSMO_TRAU_SYNCP_16_ER_CCU; + } + + ccu_descr->link.scd.demux.out_cb_bits = e1_i460_demux_bits_cb; + ccu_descr->link.scd.demux.out_cb_bytes = NULL; + ccu_descr->link.scd.demux.user_data = ccu_descr; + ccu_descr->link.scd.mux.in_cb_queue_empty = e1_i460_mux_empty_cb; + ccu_descr->link.scd.mux.user_data = ccu_descr; + + LOGPCCU(ccu_descr, LOGL_INFO, "SETUP", "adding I.460 subchannel: bit_offset=%u, num_bits=%zu\n", + ccu_descr->link.scd.bit_offset, ccu_descr->link.scd.demux.num_bits); + ccu_descr->link.schan = osmo_i460_subchan_add(ctx, &ts_descr->i460_ts, &ccu_descr->link.scd); + if (!ccu_descr->link.schan) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "adding I.460 subchannel: failed!\n"); + return -EINVAL; + } + + /* Configure TRAU synchronizer */ + ccu_descr->link.trau_sync_fi = osmo_trau_sync_alloc(tall_ccu_ctx, "trau-sync", sync_frame_out_cb, sync_pattern, ccu_descr); + if (!ccu_descr->link.trau_sync_fi) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "adding I.460 TRAU frame sync: failed!\n"); + return -EINVAL; + } + + /* Ericsson uses a different synchronization pattern for MCS9 TRAU frames */ + if (sync_pattern == OSMO_TRAU_SYNCP_64_ER_CCU) + osmo_trau_sync_set_secondary_pat(ccu_descr->link.trau_sync_fi, OSMO_TRAU_SYNCP_64_ER_CCU_MCS9, 1); + + return 0; +} + +/* Remove an I.460 subslot from the CCU descriptor */ +static void del_i460_subslot(struct er_ccu_descr *ccu_descr) +{ + if (ccu_descr->link.schan) + osmo_i460_subchan_del(ccu_descr->link.schan); + ccu_descr->link.schan = NULL; + if (ccu_descr->link.trau_sync_fi) + osmo_fsm_inst_term(ccu_descr->link.trau_sync_fi, OSMO_FSM_TERM_REGULAR, NULL); + ccu_descr->link.trau_sync_fi = NULL; + + memset(&ccu_descr->link.scd, 0, sizeof(ccu_descr->link.scd)); +} + +/* Configure an E1 timeslot according to the description in the ccu_descr */ +static int open_e1_timeslot(struct er_ccu_descr *ccu_descr) +{ + struct e1inp_line *e1_line; + struct e1_ts_descr *ts_descr; + int rc; + + /* Find timeslot descriptor and check if the timeslot is already open. */ + ts_descr = ts_descr_from_ccu_descr(ccu_descr); + if (!ts_descr) + return -EINVAL; + if (ts_descr->usecount > 0) { + LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP", "E1 timeslot already open -- using it as it is!\n"); + ts_descr->usecount++; + return 0; + } + + /* Find and set up E1 line */ + e1_line = e1inp_line_find(ccu_descr->e1_conn_pars->e1_nr); + if (!e1_line) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "no such E1 line!\n"); + return -EINVAL; + } + e1inp_line_bind_ops(e1_line, &dummy_e1_line_ops); + + /* Set up E1 timeslot */ + rc = e1inp_ts_config_raw(&e1_line->ts[ccu_descr->e1_conn_pars->e1_ts - 1], e1_line, e1_recv_cb); + if (rc < 0) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "configuration of timeslot failed!\n"); + return -EINVAL; + } + rc = e1inp_line_update(e1_line); + if (rc < 0) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "line update failed!\n"); + return -EINVAL; + } + + /* Make sure the i460 mux is ready */ + if (!ts_descr->i460_ts_initialized) { + osmo_i460_ts_init(&ts_descr->i460_ts); + ts_descr->i460_ts_initialized = true; + } + + ts_descr->usecount++; + OSMO_ASSERT(ts_descr->usecount == 1); + + return 0; +} + +/* Configure an E1 timeslot according to the description in the ccu_descr */ +static int close_e1_timeslot(struct er_ccu_descr *ccu_descr) +{ + struct e1inp_line *e1_line; + struct e1_ts_descr *ts_descr; + int rc; + + /* Find timeslot descriptor and check if the timeslot is still used by another subslot. */ + ts_descr = ts_descr_from_ccu_descr(ccu_descr); + if (!ts_descr) + return -EINVAL; + if (ts_descr->usecount > 1) { + LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP", + "E1 timeslot still in used by another subslot, leaving it open!\n"); + ts_descr->usecount--; + return 0; + } else if (ts_descr->usecount == 0) { + /* This should not be as it means we close the timeslot too often. */ + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "E1 timeslot already closed, leaving it as it is...\n"); + return -EINVAL; + } + + /* Find E1 line */ + e1_line = e1inp_line_find(ccu_descr->e1_conn_pars->e1_nr); + if (!e1_line) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "no such E1 line!\n"); + return -EINVAL; + } + + /* Release E1 timeslot */ + rc = e1inp_ts_config_none(&e1_line->ts[ccu_descr->e1_conn_pars->e1_ts - 1], e1_line); + if (rc < 0) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "failed to disable E1 timeslot!\n"); + return -EINVAL; + } + rc = e1inp_line_update(e1_line); + if (rc < 0) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "failed to update E1 line!\n"); + return -EINVAL; + } + + ts_descr->usecount--; + OSMO_ASSERT(ts_descr->usecount == 0); + + return 0; +} + +int er_ccu_if_open(struct er_ccu_descr *ccu_descr) +{ + if (ccu_descr->link.ccu_connected) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", + "cannot connect CCU since it is already connected -- ignored!\n"); + return 0; + } + + if (open_e1_timeslot(ccu_descr) < 0) + return -EINVAL; + + if (add_i460_subslot(tall_ccu_ctx, ccu_descr) < 0) + return -EINVAL; + + ccu_descr->link.ccu_connected = true; + LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP", "CCU connected.\n"); + return 0; +} + +void er_ccu_if_close(struct er_ccu_descr *ccu_descr) +{ + if (!ccu_descr->link.ccu_connected) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", + "cannot disconnect CCU since it is already disconnected -- ignored!\n"); + return; + } + + del_i460_subslot(ccu_descr); + close_e1_timeslot(ccu_descr); + + ccu_descr->link.ccu_connected = false; + LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP", "CCU disconnected.\n"); +} + +void er_ccu_if_tx(struct er_ccu_descr *ccu_descr, const ubit_t *bits, unsigned int num_bits) +{ + struct msgb *msg; + uint8_t *ptr; + + if (!ccu_descr->link.ccu_connected) { + LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "cannot TX block, CCU is disconnected -- ignored!\n"); + return; + } + + msg = msgb_alloc_c(tall_ccu_ctx, num_bits, "E1-I.460-PCU-IND-frame"); + ptr = msgb_put(msg, num_bits); + memcpy(ptr, bits, num_bits); + LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-TX", "sending %u bits: %s...\n", msgb_length(msg), + osmo_ubit_dump(msgb_data(msg), msgb_length(msg) > DEBUG_BITS_MAX ? DEBUG_BITS_MAX : msgb_length(msg))); + osmo_i460_mux_enqueue(ccu_descr->link.schan, msg); +} + +void er_ccu_if_init(void *ctx) +{ + libosmo_abis_init(ctx); + e1inp_vty_init(); + + tall_ccu_ctx = talloc_new(ctx); + memset(e1_lines, 0, sizeof(e1_lines)); +} diff --git a/src/ericsson-rbs/er_ccu_if.h b/src/ericsson-rbs/er_ccu_if.h new file mode 100644 index 00000000..40735301 --- /dev/null +++ b/src/ericsson-rbs/er_ccu_if.h @@ -0,0 +1,10 @@ +#pragma once + +#include <stdint.h> +#include <osmocom/abis/e1_input.h> +#include "er_ccu_descr.h" + +int er_ccu_if_open(struct er_ccu_descr *ccu_descr); +void er_ccu_if_close(struct er_ccu_descr *ccu_descr); +void er_ccu_if_tx(struct er_ccu_descr *ccu_descr, const ubit_t *bits, unsigned int num_bits); +void er_ccu_if_init(void *ctx); diff --git a/src/ericsson-rbs/er_ccu_l1_if.c b/src/ericsson-rbs/er_ccu_l1_if.c new file mode 100644 index 00000000..53ab7bd4 --- /dev/null +++ b/src/ericsson-rbs/er_ccu_l1_if.c @@ -0,0 +1,543 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Philipp Maier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <er_ccu_descr.h> +#include <er_ccu_if.h> + +#include <string.h> +#include <errno.h> + +#include <osmocom/pcu/pcuif_proto.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/abis.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/trau/trau_sync.h> +#include <osmocom/trau/trau_pcu_ericsson.h> +#include <osmocom/gsm/gsm0502.h> +#include <osmocom/core/talloc.h> + +#include <bts.h> +#include <pcu_l1_if.h> +#include <pcu_l1_if_phy.h> + +extern void *tall_pcu_ctx; + +const uint8_t fn_inc_table[4] = { 4, 4, 5, 0 }; +const uint8_t blk_nr_table[4] = { 4, 4, 5, 0 }; + +#define SYNC_CHECK_INTERVAL GSM_TDMA_SUPERFRAME * 8 + +/* Subtrahend to convert Ericsson adjusted (block ending) fn to regular fn (uplink only) */ +#define AFN_SUBTRAHEND 3 + +#define LOGPL1IF(ccu_descr, level, tag, fmt, args...) \ + LOGP(DL1IF, level, "%s: PDCH(trx=%u,ts=%u) E1-line(line=%u,ts=%u,ss=%u) " fmt, \ + tag, ccu_descr->pcu.trx_no, ccu_descr->pcu.ts, \ + ccu_descr->e1_conn_pars->e1_nr, ccu_descr->e1_conn_pars->e1_ts, \ + ccu_descr->e1_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL ? 0 : ccu_descr->e1_conn_pars->e1_ts_ss, \ + ## args) + +/* Calculate GPRS block number from frame number */ +static uint8_t fn_to_block_nr(uint32_t fn) +{ + /* Note: See also 3GPP TS 03.64 6.5.7.2.1, + * Mapping on the multiframe structure */ + + uint8_t rel_fn; + uint8_t super_block; + uint8_t local_block; + + rel_fn = fn % 52; + + /* Warn in case of frames that do not belong to a block */ + if (rel_fn == 12 || rel_fn == 25 || rel_fn == 38 || rel_fn == 51) + LOGP(DL1IF, LOGL_ERROR, "Frame number is referencing invalid block!\n"); + + super_block = (rel_fn / 13); + local_block = rel_fn % 13 / 4; + return super_block * 3 + local_block; +} + +static uint32_t fn_dl_advance(uint32_t fn, uint32_t n_blocks) +{ + uint32_t i; + + uint8_t inc_fn; + + for (i = 0; i < n_blocks; i++) { + inc_fn = fn_inc_table[(fn % 13) / 4]; + fn = GSM_TDMA_FN_SUM(fn, inc_fn); + } + + return fn; +} + +static bool mac_block_is_noise(struct er_gprs_trau_frame *trau_frame) +{ + switch (trau_frame->u.ccu_data_ind.cs_hdr) { + case CS_OR_HDR_CS1: + case CS_OR_HDR_CS2: + case CS_OR_HDR_CS3: + case CS_OR_HDR_CS4: + if (!trau_frame->u.ccu_data_ind.u.gprs.parity_ok) + return true; + break; + case CS_OR_HDR_HDR1: + case CS_OR_HDR_HDR2: + case CS_OR_HDR_HDR3: + if (!trau_frame->u.ccu_data_ind.u.egprs.hdr_good) + return true; + if (!trau_frame->u.ccu_data_ind.u.egprs.data_good[0] + && !trau_frame->u.ccu_data_ind.u.egprs.data_good[1]) + return true; + break; + case CS_OR_HDR_AB: + /* We are not interested in receiving access bursts. */ + return true; + } + + /* No noise, this block is interesting for us. */ + return false; +} + +static void log_data_ind(struct er_ccu_descr *ccu_descr, struct er_gprs_trau_frame *trau_frame, uint32_t afn_ul_comp, + uint32_t afn_dl_comp) +{ + switch (trau_frame->u.ccu_data_ind.cs_hdr) { + case CS_OR_HDR_CS1: + case CS_OR_HDR_CS2: + case CS_OR_HDR_CS3: + case CS_OR_HDR_CS4: + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-DATA-IND", + "tav=%u, dbe=%u, cs_hdr=%u, rx_lev=%u, est_acc_del_dev=%u," + "block_qual=%u, parity_ok=%u, data=%s<==, afn_ul_comp=%u/%u\n", trau_frame->u.ccu_data_ind.tav, + trau_frame->u.ccu_data_ind.dbe, trau_frame->u.ccu_data_ind.cs_hdr, + trau_frame->u.ccu_data_ind.rx_lev, trau_frame->u.ccu_data_ind.est_acc_del_dev, + trau_frame->u.ccu_data_ind.u.gprs.block_qual, trau_frame->u.ccu_data_ind.u.gprs.parity_ok, + osmo_hexdump_nospc(trau_frame->u.ccu_data_ind.data, trau_frame->u.ccu_data_ind.data_len), + afn_ul_comp, afn_ul_comp % 52); + break; + case CS_OR_HDR_HDR1: + case CS_OR_HDR_HDR2: + case CS_OR_HDR_HDR3: + case CS_OR_HDR_AB: + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-DATA-IND", + "tav=%u, dbe=%u, cs_hdr=%u, rx_lev=%u, est_acc_del_dev=%u," + "mean_bep=%u, cv_bep=%u, hdr_good=%u, data_good[0]=%u, data_good[1]=%u, data=%s<==, afn_ul_comp=%u/%u\n", + trau_frame->u.ccu_data_ind.tav, trau_frame->u.ccu_data_ind.dbe, + trau_frame->u.ccu_data_ind.cs_hdr, trau_frame->u.ccu_data_ind.rx_lev, + trau_frame->u.ccu_data_ind.est_acc_del_dev, trau_frame->u.ccu_data_ind.u.egprs.mean_bep, + trau_frame->u.ccu_data_ind.u.egprs.cv_bep, trau_frame->u.ccu_data_ind.u.egprs.hdr_good, + trau_frame->u.ccu_data_ind.u.egprs.data_good[0], + trau_frame->u.ccu_data_ind.u.egprs.data_good[1], + osmo_hexdump_nospc(trau_frame->u.ccu_data_ind.data, trau_frame->u.ccu_data_ind.data_len), + afn_ul_comp, afn_ul_comp % 52); + } +} + +/* Receive block from CCU */ +static void er_ccu_rx_cb(struct er_ccu_descr *ccu_descr, const ubit_t *bits, unsigned int num_bits) +{ + int rc; + struct er_gprs_trau_frame trau_frame; + uint8_t inc_ul; + uint8_t inc_dl; + uint32_t afn_ul; + uint32_t afn_dl; + uint32_t afn_ul_comp; + uint32_t afn_dl_comp; + struct pcu_l1_meas meas = { 0 }; + struct gprs_rlcmac_bts *bts; + struct gprs_rlcmac_pdch *pdch; + + /* Compute the current frame numbers from the last frame number */ + inc_ul = fn_inc_table[(ccu_descr->sync.last_afn_ul % 13) / 4]; + inc_dl = fn_inc_table[(ccu_descr->sync.last_afn_dl % 13) / 4]; + afn_ul = GSM_TDMA_FN_SUM(ccu_descr->sync.last_afn_ul, inc_ul); + afn_dl = GSM_TDMA_FN_SUM(ccu_descr->sync.last_afn_dl, inc_dl); + + /* Compute compensated frame numbers. This will be the framenumbers we + * will use to exchange blocks with the PCU code. The following applies: + * + * 1. The uplink related frame numbers sent by the ericsson CCU refer to the end of a block. This is + * compensated by subtracting three frames. + * 2. The CCU downlink frame number runs one block past the uplink frame number. This needs to be + * compesated as well (+1). + * 3. The difference between the local (PCU) and the returned (CCU) pseq counter value is the number of blocks + * that the PCU must + * shift its downlink alignment in order to compensate the link latency between PCU and CCU. */ + afn_ul_comp = GSM_TDMA_FN_SUB(afn_ul, AFN_SUBTRAHEND); + afn_dl_comp = afn_dl; + afn_dl_comp = fn_dl_advance(afn_dl_comp, GSM_TDMA_FN_DIFF(ccu_descr->sync.pseq_pcu, ccu_descr->sync.pseq_ccu) + 1); + + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-SYNC", + "afn_ul=%u/%u, afn_dl=%u/%u, afn_diff=%u => afn_ul_comp=%u/%u, afn_dl_comp=%u/%u, afn_diff_comp=%u\n", + afn_ul, afn_ul % 52, afn_dl, afn_dl % 52, GSM_TDMA_FN_DIFF(afn_ul, afn_dl), afn_ul_comp, + afn_ul_comp % 52, afn_dl_comp, afn_dl_comp % 52, GSM_TDMA_FN_DIFF(afn_ul_comp, afn_dl_comp)); + + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-SYNC", "pseq_pcu=%u, pseq_ccu=%u, pseq_diff=%u\n", + ccu_descr->sync.pseq_pcu, ccu_descr->sync.pseq_ccu, GSM_TDMA_FN_DIFF(ccu_descr->sync.pseq_pcu, ccu_descr->sync.pseq_ccu)); + + /* Decode indication from CCU */ + if (ccu_descr->e1_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL) + rc = er_gprs_trau_frame_decode_64k(&trau_frame, bits); + else + rc = er_gprs_trau_frame_decode_16k(&trau_frame, bits); + if (rc < 0) { + LOGPL1IF(ccu_descr, LOGL_ERROR, "CCU-XXXX-IND", + "unable to decode uplink TRAU frame, afn_ul_comp=%u/%u\n", afn_ul_comp, afn_ul_comp % 52); + + /* Report to the CCU that there is an issue with uplink TRAU frames, the CCU will then send + * a CCU-SYNC-IND within the next TRAU frame, so we can check if we are still in sync and trigger + * synchronization procedure if necessary. */ + ccu_descr->sync.ul_frame_err = true; + goto skip; + } + + switch (trau_frame.type) { + case ER_GPRS_TRAU_FT_SYNC: + if (trau_frame.u.ccu_sync_ind.pseq != 0x3FFFFF) { + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-SYNC-IND", + "tav=%u, dbe=%u, dfe=%u, pseq=%u, afn_ul=%u, afn_dl=%u\n", + trau_frame.u.ccu_sync_ind.tav, trau_frame.u.ccu_sync_ind.dbe, + trau_frame.u.ccu_sync_ind.dfe, trau_frame.u.ccu_sync_ind.pseq, + trau_frame.u.ccu_sync_ind.afn_ul, trau_frame.u.ccu_sync_ind.afn_dl); + + /* Synchronize the current CCU PSEQ state */ + ccu_descr->sync.pseq_ccu = trau_frame.u.ccu_sync_ind.pseq; + } else { + LOGPL1IF(ccu_descr, LOGL_DEBUG, "CCU-SYNC-IND", + "tav=%u, dbe=%u, dfe=%u, pseq=(none), afn_ul=%u, afn_dl=%u\n", + trau_frame.u.ccu_sync_ind.tav, trau_frame.u.ccu_sync_ind.dbe, + trau_frame.u.ccu_sync_ind.dfe, trau_frame.u.ccu_sync_ind.afn_ul, + trau_frame.u.ccu_sync_ind.afn_dl); + } + + ccu_descr->sync.tav = trau_frame.u.ccu_sync_ind.tav; + + /* Check if we are in sync with the CCU, if not trigger synchronization procedure */ + if (afn_ul != trau_frame.u.ccu_sync_ind.afn_ul || afn_dl != trau_frame.u.ccu_sync_ind.afn_dl) { + if (afn_ul != trau_frame.u.ccu_sync_ind.afn_ul) + LOGPL1IF(ccu_descr, LOGL_NOTICE, "CCU-SYNC-IND", + "afn_ul=%u (computed) != afn_ul=%u (sync-ind) => delta=%u\n", afn_ul, + trau_frame.u.ccu_sync_ind.afn_ul, + GSM_TDMA_FN_DIFF(afn_ul, trau_frame.u.ccu_sync_ind.afn_ul)); + if (afn_dl != trau_frame.u.ccu_sync_ind.afn_dl) + LOGPL1IF(ccu_descr, LOGL_NOTICE, "CCU-SYNC-IND", + "afn_dl=%u (computed) != afn_dl=%u (sync-ind) => delta=%u\n", afn_dl, + trau_frame.u.ccu_sync_ind.afn_dl, + GSM_TDMA_FN_DIFF(afn_dl, trau_frame.u.ccu_sync_ind.afn_dl)); + LOGPL1IF(ccu_descr, LOGL_NOTICE, "CCU-SYNC-IND", + "FN jump detected, lost sync with CCU -- (re)synchronizing...\n"); + ccu_descr->sync.ccu_synced = false; + } else { + LOGPL1IF(ccu_descr, LOGL_NOTICE, "CCU-SYNC-IND", "in sync with CCU\n"); + ccu_descr->sync.ccu_synced = true; + } + + /* Overwrite calculated afn_ul and afn_dl with the actual values from the SYNC indication */ + afn_ul = trau_frame.u.ccu_sync_ind.afn_ul; + afn_dl = trau_frame.u.ccu_sync_ind.afn_dl; + + break; + case ER_GPRS_TRAU_FT_DATA: + + ccu_descr->sync.tav = trau_frame.u.ccu_data_ind.tav; + + /* Ignore all data indications that contain only noise */ + if (mac_block_is_noise(&trau_frame)) + break; + + log_data_ind(ccu_descr, &trau_frame, afn_ul_comp, afn_dl_comp); + + /* Hand received MAC block into PCU */ + bts = gprs_pcu_get_bts_by_nr(the_pcu, ccu_descr->pcu.bts_nr); + if (!bts) + break; + meas.have_rssi = 1; + meas.rssi = rxlev2dbm(trau_frame.u.ccu_data_ind.rx_lev); + meas.have_link_qual = 1; + meas.link_qual = trau_frame.u.ccu_data_ind.u.gprs.block_qual; + pdch = &bts->trx[ccu_descr->pcu.trx_no].pdch[ccu_descr->pcu.ts]; + rc = pcu_rx_data_ind_pdtch(bts, pdch, trau_frame.u.ccu_data_ind.data, + trau_frame.u.ccu_data_ind.data_len, afn_ul_comp, &meas); + break; + default: + LOGPL1IF(ccu_descr, LOGL_ERROR, "CCU-XXXX-IND", "unhandled CCU indication!\n"); + } + +skip: + if (ccu_descr->sync.ccu_synced) { + bts = gprs_pcu_get_bts_by_nr(the_pcu, ccu_descr->pcu.bts_nr); + if (bts) { + /* The PCU timing is locked to the uplink fame number. The downlink frame number is advanced + * into the future so that the line latency is compensated and the frame arrives at the right + * point in time. */ + pdch = &bts->trx[ccu_descr->pcu.trx_no].pdch[ccu_descr->pcu.ts]; + pcu_rx_block_time(bts, pdch->trx->arfcn, afn_ul_comp, ccu_descr->pcu.ts); + rc = pcu_rx_rts_req_pdtch(bts, ccu_descr->pcu.trx_no, ccu_descr->pcu.ts, afn_dl_comp, + fn_to_block_nr(afn_dl_comp)); + } + } + + /* We do not receive sync indications in every cycle. When traffic is transferred we won't get frame numbers + * from the CCU. In this case we must update the last_afn_ul/dl values from the computed frame numbers + * (see above) */ + ccu_descr->sync.last_afn_ul = afn_ul; + ccu_descr->sync.last_afn_dl = afn_dl; + ccu_descr->sync.pseq_pcu++; + ccu_descr->sync.pseq_ccu++; +} + +static void er_ccu_empty_cb(struct er_ccu_descr *ccu_descr) +{ + struct er_gprs_trau_frame trau_frame; + ubit_t trau_frame_encoded[ER_GPRS_TRAU_FRAME_LEN_64K]; + int rc; + + memset(&trau_frame, 0, sizeof(trau_frame)); + trau_frame.u.pcu_sync_ind.pseq = ccu_descr->sync.pseq_pcu; + trau_frame.u.pcu_sync_ind.tav = ccu_descr->sync.tav; + trau_frame.u.pcu_sync_ind.fn_ul = 0x3FFFFF; + trau_frame.u.pcu_sync_ind.fn_dl = 0x3FFFFF; + trau_frame.u.pcu_sync_ind.fn_ss = 0x3FFFFF; + trau_frame.u.pcu_sync_ind.ls = 0x3FFFFF; + trau_frame.u.pcu_sync_ind.ss = 0x3FFFFF; + trau_frame.type = ER_GPRS_TRAU_FT_SYNC; + + if (ccu_descr->e1_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL) + rc = er_gprs_trau_frame_encode_64k(trau_frame_encoded, &trau_frame); + else + rc = er_gprs_trau_frame_encode_16k(trau_frame_encoded, &trau_frame); + if (rc < 0) { + LOGPL1IF(ccu_descr, LOGL_ERROR, "PCU-SYNC-IND", "unable to encode TRAU frame\n"); + return; + } + LOGPL1IF(ccu_descr, LOGL_DEBUG, "PCU-SYNC-IND", "pseq=%u, tav=%u\n", + trau_frame.u.pcu_sync_ind.pseq, trau_frame.u.pcu_sync_ind.tav); + er_ccu_if_tx(ccu_descr, trau_frame_encoded, rc); + + /* Make sure timing adjustment value is reset after use */ + ccu_descr->sync.tav = TIME_ADJ_NONE; +} + +/* use the length of the block to determine the coding scheme */ +static int cs_hdr_from_len(uint8_t len) +{ + switch (len) { + case 23: + return CS_OR_HDR_CS1; + case 34: + return CS_OR_HDR_CS2; + case 40: + return CS_OR_HDR_CS3; + case 54: + return CS_OR_HDR_CS4; + case 27: + case 33: + case 42: + case 49: + return CS_OR_HDR_HDR3; + case 60: + case 78: + return CS_OR_HDR_HDR2; + case 118: + case 142: + case 154: + return CS_OR_HDR_HDR1; + default: + return -EINVAL; + } +} + +/* send packet data request to L1 */ +int l1if_pdch_req(void *obj, uint8_t ts, int is_ptcch, uint32_t fn, + uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len) +{ + struct er_trx_descr *trx_descr = obj; + struct er_ccu_descr *ccu_descr; + struct er_gprs_trau_frame trau_frame; + ubit_t trau_frame_encoded[ER_GPRS_TRAU_FRAME_LEN_64K]; + struct gprs_rlcmac_bts *bts; + int rc; + + /* Make sure that the CCU is synchronized and connected. */ + if (!trx_descr) { + LOGP(DL1IF, LOGL_ERROR, "PCU-DATA-IND: PDCH(ts=%u, arfcn=%u) no TRX context, tossing MAC block...\n", + ts, arfcn); + return -EINVAL; + } + + ccu_descr = &trx_descr->ts_ccu_descr[ts]; + + if (!ccu_descr->link.ccu_connected) { + LOGPL1IF(ccu_descr, LOGL_NOTICE, "PCU-DATA-IND", "CCU not connected, tossing MAC block...\n"); + return -EINVAL; + } + if (!ccu_descr->sync.ccu_synced) { + LOGPL1IF(ccu_descr, LOGL_NOTICE, "PCU-DATA-IND", "CCU not synchronized, tossing MAC block...\n"); + return -EINVAL; + } + + /* Hand received MAC block into PCU */ + bts = gprs_pcu_get_bts_by_nr(the_pcu, ccu_descr->pcu.bts_nr); + if (!bts) { + LOGPL1IF(ccu_descr, LOGL_NOTICE, "PCU-DATA-IND", "no BTS, tossing MAC block...\n"); + return -EINVAL; + } + + memset(&trau_frame, 0, sizeof(trau_frame)); + trau_frame.type = ER_GPRS_TRAU_FT_DATA; + + rc = cs_hdr_from_len(len); + if (rc < 0) { + LOGPL1IF(ccu_descr, LOGL_ERROR, "PCU-DATA-IND", + "unable to encode TRAU frame, invalid CS or MCS value set\n"); + return -EINVAL; + } + trau_frame.u.pcu_data_ind.cs_hdr = (enum er_cs_or_hdr)rc; + trau_frame.u.pcu_data_ind.tav = ccu_descr->sync.tav; + trau_frame.u.pcu_data_ind.ul_frame_err = ccu_descr->sync.ul_frame_err; + if (bts->mcs_mask) + trau_frame.u.pcu_data_ind.ul_chan_mode = ER_UL_CHMOD_NB_UNKN; + else + trau_frame.u.pcu_data_ind.ul_chan_mode = ER_UL_CHMOD_NB_GMSK; + OSMO_ASSERT(len < sizeof(trau_frame.u.pcu_data_ind.data)); + memcpy(trau_frame.u.pcu_data_ind.data, data, len); + + /* Regulary ignore one MAC block in uplink. The CCU will then send one CCU-SYNC-IND instead. We use this + * indication to check whether we are still in sync with the CCU. */ + if (fn % SYNC_CHECK_INTERVAL == 0) + trau_frame.u.pcu_data_ind.ul_chan_mode = ER_UL_CHMOD_VOID; + + if (ccu_descr->e1_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL) + rc = er_gprs_trau_frame_encode_64k(trau_frame_encoded, &trau_frame); + else + rc = er_gprs_trau_frame_encode_16k(trau_frame_encoded, &trau_frame); + if (rc < 0) { + LOGPL1IF(ccu_descr, LOGL_ERROR, "PCU-DATA-IND", "unable to encode TRAU frame\n"); + return -EINVAL; + } + LOGPL1IF(ccu_descr, LOGL_DEBUG, "PCU-DATA-IND", + "tav=%u, ul_frame_err=%u, cs_hdr=%u, ul_chan_mode=%u, atten_db=%u, timing_offset=%u," + " data=%s==>, fn=%u/%u (comp)\n", trau_frame.u.pcu_data_ind.tav, + trau_frame.u.pcu_data_ind.ul_frame_err, trau_frame.u.pcu_data_ind.cs_hdr, + trau_frame.u.pcu_data_ind.ul_chan_mode, trau_frame.u.pcu_data_ind.atten_db, + trau_frame.u.pcu_data_ind.timing_offset, osmo_hexdump_nospc(trau_frame.u.pcu_data_ind.data, len), fn, + fn % 52); + er_ccu_if_tx(ccu_descr, trau_frame_encoded, rc); + + /* Make sure timing adjustment value is reset after use */ + ccu_descr->sync.tav = TIME_ADJ_NONE; + ccu_descr->sync.ul_frame_err = false; + + return 0; +} + +void *l1if_open_trx(uint8_t bts_nr, uint8_t trx_no, uint32_t hlayer1, struct gsmtap_inst *gsmtap) +{ + struct er_trx_descr *trx_descr; + unsigned int i; + + /* Note: We do not have enough information to really open anything at + * this point. We will just create the TRX context and fill it wit basic + * CCU context (one for each TS) */ + + trx_descr = talloc_zero(tall_pcu_ctx, struct er_trx_descr); + OSMO_ASSERT(trx_descr); + + for (i = 0; i < ARRAY_SIZE(trx_descr->ts_ccu_descr); i++) { + trx_descr->ts_ccu_descr[i].er_ccu_rx_cb = er_ccu_rx_cb; + trx_descr->ts_ccu_descr[i].er_ccu_empty_cb = er_ccu_empty_cb; + trx_descr->ts_ccu_descr[i].pcu.trx_no = trx_no; + trx_descr->ts_ccu_descr[i].pcu.bts_nr = bts_nr; + trx_descr->ts_ccu_descr[i].pcu.ts = i; + } + + return trx_descr; +} + +int l1if_close_trx(void *obj) +{ + struct er_trx_descr *trx_descr = obj; + unsigned int i; + + if (!trx_descr) { + LOGP(DL1IF, LOGL_ERROR, "PCU-DATA-IND: no TRX context, cannot close unknown TRX...\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(trx_descr->ts_ccu_descr); i++) + er_ccu_if_close(&trx_descr->ts_ccu_descr[i]); + + talloc_free(trx_descr); + return 0; +} + +int l1if_connect_pdch(void *obj, uint8_t ts) +{ + struct er_trx_descr *trx_descr = obj; + struct er_ccu_descr *ccu_descr; + int rc; + + if (!trx_descr) { + LOGP(DL1IF, LOGL_ERROR, "SETUP: PDCH(ts=%u) no CCU context, TRX never opened before?\n", ts); + return -EINVAL; + } + + ccu_descr = &trx_descr->ts_ccu_descr[ts]; + + rc = pcu_l1if_get_e1_ccu_conn_pars(&ccu_descr->e1_conn_pars, ccu_descr->pcu.bts_nr, ccu_descr->pcu.trx_no, + ccu_descr->pcu.ts); + if (rc < 0) { + LOGPL1IF(ccu_descr, LOGL_ERROR, "SETUP", "cannot find E1 connection parameters for CCU\n"); + return -EINVAL; + } + + rc = er_ccu_if_open(ccu_descr); + if (rc < 0) + return -EINVAL; + + return 0; +} + +int l1if_disconnect_pdch(void *obj, uint8_t ts) +{ + struct er_trx_descr *trx_descr = obj; + struct er_ccu_descr *ccu_descr; + + if (!trx_descr) { + LOGP(DL1IF, LOGL_ERROR, "SETUP: PDCH(ts=%u) no TRX context, TRX never opened before?\n", ts); + return -EINVAL; + } + + ccu_descr = &trx_descr->ts_ccu_descr[ts]; + + er_ccu_if_close(ccu_descr); + + return 0; +} + +int l1if_init(void) +{ + er_ccu_if_init(tall_pcu_ctx); + return 0; +} diff --git a/src/gprs_bssgp_pcu.c b/src/gprs_bssgp_pcu.c index 1fcacdb1..2c5a97a7 100644 --- a/src/gprs_bssgp_pcu.c +++ b/src/gprs_bssgp_pcu.c @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <gprs_rlcmac.h> @@ -39,6 +35,8 @@ #include "tbf_dl.h" #include "llc.h" #include "gprs_rlcmac.h" +#include "bts_pch_timer.h" +#include "alloc_algo.h" /* Tuning parameters for BSSGP flow control */ #define FC_DEFAULT_LIFE_TIME_SECS 10 /* experimental value, 10s */ @@ -107,12 +105,8 @@ static int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp) uint8_t egprs_ms_class = 0; int rc; MS_Radio_Access_capability_t rac; - /* TODO: is it really necessary to initialize this as a "000" IMSI? It seems, the function should just return an - * error if no IMSI IE was found. */ - struct osmo_mobile_identity mi_imsi = { - .type = GSM_MI_TYPE_TMSI, - }; - OSMO_STRLCPY_ARRAY(mi_imsi.imsi, "000"); + const char *imsi = NULL; + struct osmo_mobile_identity mi_imsi; budh = (struct bssgp_ud_hdr *)msgb_bssgph(msg); tlli = ntohl(budh->tlli); @@ -143,6 +137,7 @@ static int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp) LOGP(DBSSGP, LOGL_NOTICE, "Failed to parse IMSI IE (rc=%d)\n", rc); return bssgp_tx_status(BSSGP_CAUSE_COND_IE_ERR, NULL, msg); } + imsi = &mi_imsi.imsi[0]; } /* parse ms radio access capability */ @@ -179,10 +174,11 @@ static int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp) "TLLI (old) IE\n"); } - LOGP(DBSSGP, LOGL_INFO, "LLC [SGSN -> PCU] = TLLI: 0x%08x IMSI: %s len: %d\n", tlli, mi_imsi.imsi, len); + LOGP(DBSSGP, LOGL_INFO, "LLC [SGSN -> PCU] = TLLI: 0x%08x IMSI: %s len: %d\n", + tlli, imsi ? : "none", len); - return dl_tbf_handle(the_pcu->bssgp.bts, tlli, tlli_old, mi_imsi.imsi, - ms_class, egprs_ms_class, delay_csec, data, len); + return dl_tbf_handle(the_pcu->bssgp.bts, tlli, tlli_old, imsi, ms_class, + egprs_ms_class, delay_csec, data, len); } /* 3GPP TS 48.018 Table 10.3.2. Returns 0 on success, suggested BSSGP cause otherwise */ @@ -248,9 +244,9 @@ static int gprs_bssgp_pcu_rx_paging_cs(struct msgb *msg, const struct tlv_parsed * target MS is using. */ llist_for_each_entry(bts, &the_pcu->bts_list, list) { /* TODO: Match by TMSI before IMSI if present?! */ - ms = bts_ms_by_tlli(bts, req.tlli, req.tlli); + ms = bts_get_ms_by_tlli(bts, req.tlli, req.tlli); if (!ms && req.mi_imsi_present) - ms = bts_ms_by_imsi(bts, req.mi_imsi.imsi); + ms = bts_get_ms_by_imsi(bts, req.mi_imsi.imsi); bts_add_paging(bts, &req, ms); } @@ -293,7 +289,6 @@ static int gprs_bssgp_pcu_rx_paging_ps(struct msgb *msg, const struct tlv_parsed struct osmo_mobile_identity mi_imsi; struct osmo_mobile_identity paging_mi; struct gprs_rlcmac_bts *bts; - uint16_t pgroup; int rc; rate_ctr_inc(rate_ctr_group_get_ctr(the_pcu->bssgp.ctrs, SGSN_CTR_RX_PAGING_PS)); @@ -308,18 +303,20 @@ static int gprs_bssgp_pcu_rx_paging_ps(struct msgb *msg, const struct tlv_parsed LOGP(DBSSGP, LOGL_NOTICE, "Failed to parse IMSI IE (rc=%d)\n", rc); return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg); } - pgroup = imsi2paging_group(mi_imsi.imsi); - if (pgroup > 999) { - LOGP(DBSSGP, LOGL_NOTICE, "Failed to compute IMSI %s paging group\n", mi_imsi.imsi); - return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg); - } if ((rc = get_paging_ps_mi(&paging_mi, tp)) > 0) return bssgp_tx_status((enum gprs_bssgp_cause) rc, NULL, msg); /* FIXME: look if MS is attached a specific BTS and then only page on that one? */ llist_for_each_entry(bts, &the_pcu->bts_list, list) { - gprs_rlcmac_paging_request(bts, &paging_mi, pgroup); + if (bts_pch_timer_get_by_imsi(bts, mi_imsi.imsi)) { + LOGP(DBSSGP, LOGL_INFO, "PS-Paging request already pending for IMSI=%s\n", mi_imsi.imsi); + bts_do_rate_ctr_inc(bts, CTR_PCH_REQUESTS_ALREADY); + continue; + } + if (gprs_rlcmac_paging_request(bts, &paging_mi, mi_imsi.imsi) < 0) + continue; + bts_pch_timer_start(bts, &paging_mi, mi_imsi.imsi); } return 0; } @@ -780,7 +777,7 @@ static unsigned count_pdch(const struct gprs_rlcmac_bts *bts) for (ts_no = 0; ts_no < ARRAY_SIZE(trx->pdch); ++ts_no) { const struct gprs_rlcmac_pdch *pdch = &trx->pdch[ts_no]; - if (pdch->m_is_enabled) + if (pdch_is_enabled(pdch)) num_pdch += 1; } } @@ -1118,7 +1115,8 @@ static int ns_configure_nse(struct gprs_rlcmac_bts *bts, if (!(valid & (1 << i))) continue; - if (!gprs_ns2_ip_bind_by_sockaddr(the_pcu->nsi, &local[i])) { + bind[i] = gprs_ns2_ip_bind_by_sockaddr(the_pcu->nsi, &local[i]); + if (!bind[i]) { snprintf(name, sizeof(name), "pcu%u", i); rc = gprs_ns2_ip_bind(the_pcu->nsi, name, &local[i], 0, &bind[i]); if (rc < 0) { diff --git a/src/gprs_bssgp_pcu.h b/src/gprs_bssgp_pcu.h index 907e6663..0c0f0919 100644 --- a/src/gprs_bssgp_pcu.h +++ b/src/gprs_bssgp_pcu.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef GPRS_BSSGP_PCU_H diff --git a/src/gprs_bssgp_rim.c b/src/gprs_bssgp_rim.c index f1679a6b..aa636b66 100644 --- a/src/gprs_bssgp_rim.c +++ b/src/gprs_bssgp_rim.c @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <osmocom/gprs/gprs_bssgp.h> @@ -156,7 +152,7 @@ static int handle_ran_info_response_nacc(const struct bssgp_ran_inf_app_cont_nac } entry = si_cache_add(bts->pcu->si_cache, &nacc->reprt_cell, &val); - llist_for_each(tmp, bts_ms_list(bts)) { + llist_for_each(tmp, &bts->ms_list) { struct GprsMs *ms = llist_entry(tmp, typeof(*ms), list); if (ms->nacc && nacc_fsm_is_waiting_si_resolution(ms->nacc, &nacc->reprt_cell)) osmo_fsm_inst_dispatch(ms->nacc->fi, NACC_EV_RX_SI, entry); diff --git a/src/gprs_bssgp_rim.h b/src/gprs_bssgp_rim.h index 5fac56f1..cf46b0ed 100644 --- a/src/gprs_bssgp_rim.h +++ b/src/gprs_bssgp_rim.h @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once diff --git a/src/gprs_codel.c b/src/gprs_codel.c index 7c3b2d42..9c5737f6 100644 --- a/src/gprs_codel.c +++ b/src/gprs_codel.c @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "gprs_codel.h" diff --git a/src/gprs_codel.h b/src/gprs_codel.h index 3ad7efce..10ba91f9 100644 --- a/src/gprs_codel.h +++ b/src/gprs_codel.h @@ -19,10 +19,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once diff --git a/src/gprs_debug.cpp b/src/gprs_debug.c index 0cbc4880..ba9aeec1 100644 --- a/src/gprs_debug.cpp +++ b/src/gprs_debug.c @@ -1,7 +1,8 @@ -/* gprs_debug.cpp +/* gprs_debug.c * * Copyright (C) 2012 Ivan Klyuchnikov * Copyright (C) 2019 Harald Welte <laforge@gnumonks.org> + * Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -12,16 +13,10 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -extern "C" { #include <osmocom/core/utils.h> #include <osmocom/core/logging.h> -} #include <gprs_debug.h> @@ -84,6 +79,13 @@ static const struct log_info_cat default_categories[] = { .loglevel = LOGL_NOTICE, .enabled = 1, }, + [DMS] = { + .name = "DMS", + .color = "\033[1;34m", + .description = "Mobile Station (MS)", + .loglevel = LOGL_NOTICE, + .enabled = 1, + }, [DTBF] = { .name = "DTBF", .color = "\033[1;34m", @@ -133,6 +135,13 @@ static const struct log_info_cat default_categories[] = { .loglevel = LOGL_NOTICE, .enabled = 1, }, + [DE1] = { + .name = "DE1", + .color = "\033[1;31m", + .description = "E1 line handling", + .loglevel = LOGL_NOTICE, + .enabled = 1, + }, }; static int filter_fn(const struct log_context *ctx, diff --git a/src/gprs_debug.h b/src/gprs_debug.h index cebeabce..b66de90c 100644 --- a/src/gprs_debug.h +++ b/src/gprs_debug.h @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -23,9 +19,6 @@ extern "C" { #endif #include <osmocom/core/logging.h> -#ifdef __cplusplus -}; -#endif /* we used to have DBSSGP definded in each application, and applications telling * libosmogb which sub-system to use. That creates problems and has been deprecated */ @@ -41,6 +34,7 @@ enum { DRLCMACUL, DRLCMACSCHED, DRLCMACMEAS, + DMS, DTBF, DTBFDL, DTBFUL, @@ -48,7 +42,12 @@ enum { DPCU, DNACC, DRIM, + DE1, aDebug_LastEntry }; extern const struct log_info gprs_log_info; + +#ifdef __cplusplus +}; +#endif diff --git a/src/gprs_ms.c b/src/gprs_ms.c index 0d6be4d5..ff7316b9 100644 --- a/src/gprs_ms.c +++ b/src/gprs_ms.c @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ @@ -27,6 +23,8 @@ #include "gprs_codel.h" #include "pcu_utils.h" #include "nacc_fsm.h" +#include "tbf_ul_ack_fsm.h" +#include "alloc_algo.h" #include <time.h> @@ -64,72 +62,108 @@ static int64_t now_msec() return (int64_t)(ts.tv_sec) * 1000 + ts.tv_nsec / 1000000; } -void gprs_default_cb_ms_idle(struct GprsMs *ms) -{ - talloc_free(ms); -} +static void ms_becomes_idle(struct GprsMs *ms); -void gprs_default_cb_ms_active(struct GprsMs *ms) +static int ms_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line) { - /* do nothing */ -} + struct GprsMs *ms = e->use_count->talloc_object; + int32_t total; + int level; + char buf[1024]; -static struct gpr_ms_callback gprs_default_cb = { - .ms_idle = gprs_default_cb_ms_idle, - .ms_active = gprs_default_cb_ms_active, -}; + if (!e->use) + return -EINVAL; + + total = osmo_use_count_total(&ms->use_count); + + if (total == 0 + || (total == 1 && old_use_count == 0 && e->count == 1)) + level = LOGL_INFO; + else + level = LOGL_DEBUG; + + + LOGPSRC(DMS, level, file, line, "%s: %s %s: now used by %s\n", + ms_name(ms), + (e->count - old_use_count) > 0 ? "+" : "-", e->use, + (osmo_use_count_to_str_buf(buf, sizeof(buf), &ms->use_count), buf)); + + if (e->count < 0) + return -ERANGE; + + if (total == 0) { + OSMO_ASSERT(ms_is_idle(ms)); + ms_becomes_idle(ms); + } + return 0; +} static void ms_release_timer_cb(void *data) { struct GprsMs *ms = (struct GprsMs *) data; - LOGPMS(ms, DRLCMAC, LOGL_INFO, "Release timer expired\n"); + LOGPMS(ms, DMS, LOGL_INFO, "Release timer expired\n"); - if (ms->timer.data) { - ms->timer.data = NULL; - ms_unref(ms); - } + /* Finally free the MS after being idle for a while according to config */ + talloc_free(ms); +} + +static void ms_llc_timer_cb(void *_ms) +{ + struct GprsMs *ms = _ms; + struct gprs_rlcmac_dl_tbf *dl_tbf = ms_dl_tbf(ms); + + if (!dl_tbf) + return; + if (tbf_state(dl_tbf_as_tbf_const(dl_tbf)) != TBF_ST_FLOW) + return; + + LOGPTBFDL(dl_tbf, LOGL_DEBUG, "LLC receive timeout, requesting DL ACK\n"); + + dl_tbf_request_dl_ack(dl_tbf); } static int ms_talloc_destructor(struct GprsMs *ms); -struct GprsMs *ms_alloc(struct gprs_rlcmac_bts *bts, uint32_t tlli) +struct GprsMs *ms_alloc(struct gprs_rlcmac_bts *bts, const char *use_ref) { struct GprsMs *ms = talloc_zero(tall_pcu_ctx, struct GprsMs); + OSMO_ASSERT(bts); talloc_set_destructor(ms, ms_talloc_destructor); + llist_add(&ms->list, &bts->ms_list); + bts_stat_item_inc(bts, STAT_MS_PRESENT); + ms->bts = bts; - ms->cb = gprs_default_cb; - ms->tlli = tlli; + ms->tlli = GSM_RESERVED_TMSI; ms->new_ul_tlli = GSM_RESERVED_TMSI; ms->new_dl_tlli = GSM_RESERVED_TMSI; ms->ta = GSM48_TA_INVALID; ms->current_cs_ul = UNKNOWN; ms->current_cs_dl = UNKNOWN; - ms->is_idle = true; - INIT_LLIST_HEAD(&ms->list); INIT_LLIST_HEAD(&ms->old_tbfs); + ms->use_count = (struct osmo_use_count){ + .talloc_object = ms, + .use_cb = ms_use_cb, + }; + int codel_interval = LLC_CODEL_USE_DEFAULT; - LOGP(DRLCMAC, LOGL_INFO, "Creating MS object, TLLI = 0x%08x\n", tlli); + LOGP(DMS, LOGL_INFO, "Creating MS object\n"); ms->imsi[0] = '\0'; - memset(&ms->timer, 0, sizeof(ms->timer)); - ms->timer.cb = ms_release_timer_cb; - llc_queue_init(&ms->llc_queue); + osmo_timer_setup(&ms->release_timer, ms_release_timer_cb, ms); + llc_queue_init(&ms->llc_queue, ms); + memset(&ms->llc_timer, 0, sizeof(ms->llc_timer)); + osmo_timer_setup(&ms->llc_timer, ms_llc_timer_cb, ms); ms_set_mode(ms, GPRS); - if (ms->bts) - codel_interval = the_pcu->vty.llc_codel_interval_msec; + codel_interval = the_pcu->vty.llc_codel_interval_msec; + if (codel_interval == LLC_CODEL_USE_DEFAULT) + codel_interval = GPRS_CODEL_SLOW_INTERVAL_MS; + llc_queue_set_codel_interval(&ms->llc_queue, codel_interval); - if (codel_interval) { - if (codel_interval == LLC_CODEL_USE_DEFAULT) - codel_interval = GPRS_CODEL_SLOW_INTERVAL_MS; - ms->codel_state = talloc(ms, struct gprs_codel); - gprs_codel_init(ms->codel_state); - gprs_codel_set_interval(ms->codel_state, codel_interval); - } ms->last_cs_not_low = now_msec(); ms->app_info_pending = false; @@ -137,6 +171,8 @@ struct GprsMs *ms_alloc(struct gprs_rlcmac_bts *bts, uint32_t tlli) if (!ms->ctrs) goto free_ret; + if (use_ref) + ms_ref(ms, use_ref); return ms; free_ret: talloc_free(ms); @@ -147,99 +183,79 @@ static int ms_talloc_destructor(struct GprsMs *ms) { struct llist_item *pos, *tmp; - LOGPMS(ms, DRLCMAC, LOGL_INFO, "Destroying MS object\n"); + LOGPMS(ms, DMS, LOGL_INFO, "Destroying MS object\n"); + + bts_stat_item_dec(ms->bts, STAT_MS_PRESENT); + llist_del(&ms->list); ms_set_reserved_slots(ms, NULL, 0, 0); - if (osmo_timer_pending(&ms->timer)) - osmo_timer_del(&ms->timer); + osmo_timer_del(&ms->release_timer); if (ms->ul_tbf) { - tbf_set_ms((struct gprs_rlcmac_tbf *)ms->ul_tbf, NULL); - ms->ul_tbf = NULL; + tbf_free(ul_tbf_as_tbf(ms->ul_tbf)); + OSMO_ASSERT(ms->ul_tbf == NULL); } if (ms->dl_tbf) { - tbf_set_ms((struct gprs_rlcmac_tbf *)ms->dl_tbf, NULL); - ms->dl_tbf = NULL; + tbf_free(dl_tbf_as_tbf(ms->dl_tbf)); + OSMO_ASSERT(ms->dl_tbf == NULL); } llist_for_each_entry_safe(pos, tmp, &ms->old_tbfs, list) { struct gprs_rlcmac_tbf *tbf = (struct gprs_rlcmac_tbf *)pos->entry; - tbf_set_ms(tbf, NULL); + tbf_free(tbf); } llc_queue_clear(&ms->llc_queue, ms->bts); + osmo_timer_del(&ms->llc_timer); if (ms->ctrs) rate_ctr_group_free(ms->ctrs); return 0; } - -void ms_set_callback(struct GprsMs *ms, struct gpr_ms_callback *cb) +/* MS has no attached TBFs anymore. */ +static void ms_becomes_idle(struct GprsMs *ms) { - if (cb) - ms->cb = *cb; - else - ms->cb = gprs_default_cb; -} + unsigned long delay_rel_sec = osmo_tdef_get(ms->bts->pcu->T_defs, -2030, OSMO_TDEF_S, -1); -static void ms_update_status(struct GprsMs *ms) -{ - if (ms->ref > 0) - return; + osmo_gettimeofday(&ms->tv_idle_start, NULL); - if (ms_is_idle(ms) && !ms->is_idle) { - ms->is_idle = true; - ms->cb.ms_idle(ms); - /* this can be deleted by now, do not access it */ + ms_set_reserved_slots(ms, NULL, 0, 0); + ms->first_common_ts = NULL; + + /* Immediate free(): + * Skip delaying free() through release timer if delay is configured to be 0. + * This is useful for synced freed during unit tests. + */ + if (delay_rel_sec == 0) { + talloc_free(ms); return; } - if (!ms_is_idle(ms) && ms->is_idle) { - ms->is_idle = false; - ms->cb.ms_active(ms); - } -} - -struct GprsMs *ms_ref(struct GprsMs *ms) -{ - ms->ref += 1; - return ms; -} - -void ms_unref(struct GprsMs *ms) -{ - OSMO_ASSERT(ms->ref >= 0); - ms->ref -= 1; - if (ms->ref == 0) - ms_update_status(ms); -} - -static void ms_release_timer_start(struct GprsMs *ms) -{ - if (ms->delay == 0) + /* Immediate free(): + * Skip delaying free() through release timer if TMSI is not + * known, since those cannot really be reused. + */ + if (ms_tlli(ms) == GSM_RESERVED_TMSI) { + talloc_free(ms); return; + } - LOGPMS(ms, DRLCMAC, LOGL_DEBUG, "Schedule MS release in %u secs\n", ms->delay); - - if (!ms->timer.data) - ms->timer.data = ms_ref(ms); - - osmo_timer_schedule(&ms->timer, ms->delay, 0); + LOGPMS(ms, DMS, LOGL_INFO, "Schedule MS release in %lu secs\n", delay_rel_sec); + osmo_timer_schedule(&ms->release_timer, delay_rel_sec, 0); } -static void ms_release_timer_stop(struct GprsMs *ms) +static void ms_becomes_active(struct GprsMs *ms) { - if (!ms->timer.data) + if (!osmo_timer_pending(&ms->release_timer)) return; - LOGPMS(ms, DRLCMAC, LOGL_DEBUG, "Cancel scheduled MS release\n"); + LOGPMS(ms, DMS, LOGL_DEBUG, "Cancel scheduled MS release\n"); - osmo_timer_del(&ms->timer); - ms->timer.data = NULL; - ms_unref(ms); + timerclear(&ms->tv_idle_start); + osmo_timer_del(&ms->release_timer); } void ms_set_mode(struct GprsMs *ms, enum mcs_kind mode) @@ -293,109 +309,161 @@ void ms_set_mode(struct GprsMs *ms, enum mcs_kind mode) } } -static void ms_attach_ul_tbf(struct GprsMs *ms, struct gprs_rlcmac_ul_tbf *tbf) +/* If a TBF is attached to an MS, it is either in ms->{dl,ul}_tbf or in ms->old_tbfs list */ +static bool ms_tbf_is_attached(const struct GprsMs *ms, const struct gprs_rlcmac_tbf *tbf) { - if (ms->ul_tbf == tbf) - return; + const struct llist_item *pos; + OSMO_ASSERT(ms); + OSMO_ASSERT(tbf); + OSMO_ASSERT(tbf_ms(tbf) == ms); + + if (tbf == ul_tbf_as_tbf_const(ms->ul_tbf)) + return true; + + if (tbf == dl_tbf_as_tbf_const(ms->dl_tbf)) + return true; - LOGPMS(ms, DRLCMAC, LOGL_INFO, "Attaching UL TBF: %s\n", tbf_name((struct gprs_rlcmac_tbf *)tbf)); + llist_for_each_entry(pos, &ms->old_tbfs, list) { + const struct gprs_rlcmac_tbf *tmp_tbf = (struct gprs_rlcmac_tbf *)pos->entry; + if (tmp_tbf == tbf) + return true; + } + return false; +} - ms_ref(ms); +static void ms_attach_ul_tbf(struct GprsMs *ms, struct gprs_rlcmac_ul_tbf *tbf) +{ + LOGPMS(ms, DMS, LOGL_INFO, "Attaching UL TBF: %s\n", tbf_name((struct gprs_rlcmac_tbf *)tbf)); if (ms->ul_tbf) - llist_add_tail(tbf_ms_list((struct gprs_rlcmac_tbf *)ms->ul_tbf), &ms->old_tbfs); + llist_add_tail(tbf_ms_list(ul_tbf_as_tbf(ms->ul_tbf)), &ms->old_tbfs); ms->ul_tbf = tbf; - if (tbf) - ms_release_timer_stop(ms); - - ms_unref(ms); + ms_ref(ms, MS_USE_TBF); } static void ms_attach_dl_tbf(struct GprsMs *ms, struct gprs_rlcmac_dl_tbf *tbf) { - if (ms->dl_tbf == tbf) - return; - - LOGPMS(ms, DRLCMAC, LOGL_INFO, "Attaching DL TBF: %s\n", tbf_name((struct gprs_rlcmac_tbf *)tbf)); - - ms_ref(ms); + LOGPMS(ms, DMS, LOGL_INFO, "Attaching DL TBF: %s\n", tbf_name((struct gprs_rlcmac_tbf *)tbf)); if (ms->dl_tbf) - llist_add_tail(tbf_ms_list((struct gprs_rlcmac_tbf *)ms->dl_tbf), &ms->old_tbfs); + llist_add_tail(tbf_ms_list(dl_tbf_as_tbf(ms->dl_tbf)), &ms->old_tbfs); ms->dl_tbf = tbf; - if (tbf) - ms_release_timer_stop(ms); - - ms_unref(ms); + ms_ref(ms, MS_USE_TBF); } void ms_attach_tbf(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf) { + OSMO_ASSERT(ms); + OSMO_ASSERT(tbf); + OSMO_ASSERT(!ms_tbf_is_attached(ms, tbf)); + if (tbf_direction(tbf) == GPRS_RLCMAC_DL_TBF) - ms_attach_dl_tbf(ms, as_dl_tbf(tbf)); + ms_attach_dl_tbf(ms, tbf_as_dl_tbf(tbf)); else - ms_attach_ul_tbf(ms, as_ul_tbf(tbf)); + ms_attach_ul_tbf(ms, tbf_as_ul_tbf(tbf)); + + ms_becomes_active(ms); } void ms_detach_tbf(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf) { - if (tbf == (struct gprs_rlcmac_tbf *)(ms->ul_tbf)) { - ms->ul_tbf = NULL; - } else if (tbf == (struct gprs_rlcmac_tbf *)(ms->dl_tbf)) { - ms->dl_tbf = NULL; - } else { - bool found = false; - - struct llist_item *pos, *tmp; - llist_for_each_entry_safe(pos, tmp, &ms->old_tbfs, list) { - struct gprs_rlcmac_tbf *tmp_tbf = (struct gprs_rlcmac_tbf *)pos->entry; - if (tmp_tbf == tbf) { - llist_del(&pos->list); - found = true; - break; - } - } + OSMO_ASSERT(tbf_ms(tbf) == ms); - /* Protect against recursive calls via set_ms() */ - if (!found) - return; - } + /* In general this should not happen, but it can happen if during TBF + * allocation something fails before tbf->setup() called ms_attach_tbf(). */ + if (!ms_tbf_is_attached(ms, tbf)) + return; - LOGPMS(ms, DRLCMAC, LOGL_INFO, "Detaching TBF: %s\n", + LOGPMS(ms, DMS, LOGL_INFO, "Detaching TBF: %s\n", tbf_name(tbf)); - if (tbf_ms(tbf) == ms) - tbf_set_ms(tbf, NULL); - - if (!ms->dl_tbf && !ms->ul_tbf) { - ms_set_reserved_slots(ms, NULL, 0, 0); - - if (ms_tlli(ms) != 0) - ms_release_timer_start(ms); + if (tbf == ul_tbf_as_tbf(ms->ul_tbf)) { + ms->ul_tbf = NULL; + } else if (tbf == dl_tbf_as_tbf(ms->dl_tbf)) { + ms->dl_tbf = NULL; + } else { + /* We know from ms_tbf_is_attached()==true check above that tbf + * is in ms->old_tbfs, no need to look it up again. */ + llist_del(tbf_ms_list(tbf)); } - ms_update_status(ms); + ms_unref(ms, MS_USE_TBF); } -void ms_reset(struct GprsMs *ms) +/* Cleans up old MS being merged into a new one. Should be called with a + * ms_ref() taken to avoid use-after-free. + */ +static void ms_reset(struct GprsMs *ms) { - LOGPMS(ms, DRLCMAC, LOGL_INFO, "Clearing MS object\n"); - - ms_release_timer_stop(ms); + LOGPMS(ms, DMS, LOGL_INFO, "Clearing MS object\n"); + struct llist_item *pos; + struct gprs_rlcmac_tbf *tbf; + + tbf = ul_tbf_as_tbf(ms_ul_tbf(ms)); + if (tbf && !tbf_timers_pending(tbf, T_MAX)) + tbf_free(tbf); + tbf = dl_tbf_as_tbf(ms_dl_tbf(ms)); + if (tbf && !tbf_timers_pending(tbf, T_MAX)) + tbf_free(tbf); + + while ((pos = llist_first_entry_or_null(&ms->old_tbfs, struct llist_item, list))) { + tbf = (struct gprs_rlcmac_tbf *)pos->entry; + if (!tbf_timers_pending(tbf, T_MAX)) + tbf_free(tbf); + } + /* Flag it with invalid data so that it cannot be looked up anymore and + * shows up specially if listed in VTY. Furthermore, it will also trigger + * immediate free() when it becomes idle: */ ms->tlli = GSM_RESERVED_TMSI; ms->new_dl_tlli = ms->tlli; ms->new_ul_tlli = ms->tlli; ms->imsi[0] = '\0'; } -static void ms_merge_old_ms(struct GprsMs *ms, struct GprsMs *old_ms) +/* This function should be called on the MS object of a TBF each time an RLCMAC + * block is received for it with TLLI information. + * Besides updating the TLLI field on the MS object, it also seeks for other MS + * objects in the store and merges them into the current MS object. The MS + * duplication happened because we don't learn the TLLI of the created TBF until + * a later point. */ +void ms_update_announced_tlli(struct GprsMs *ms, uint32_t tlli) { + struct GprsMs *old_ms = NULL; + + if (tlli == GSM_RESERVED_TMSI) + return; + + /* When the TLLI does not match the ms, check if there is another + * MS object that belongs to that TLLI and if yes make sure one of them + * gets deleted. */ + if (!ms_check_tlli(ms, tlli)) + old_ms = bts_get_ms_by_tlli(ms->bts, tlli, GSM_RESERVED_TMSI); + + ms_set_tlli(ms, tlli); + + if (old_ms) + ms_merge_and_clear_ms(ms, old_ms); + /* old_ms may no longer be available here */ +} + +/* Merge 'old_ms' object into 'ms' object. + * 'old_ms' may be freed during the call to this function, don't use the pointer to it afterwards */ +void ms_merge_and_clear_ms(struct GprsMs *ms, struct GprsMs *old_ms) +{ + char old_ms_name[128]; + struct gprs_rlcmac_dl_tbf *dl_tbf; + OSMO_ASSERT(old_ms != ms); + ms_ref(old_ms, __func__); + + ms_name_buf(old_ms, old_ms_name, sizeof(old_ms_name)); + + LOGPMS(ms, DMS, LOGL_INFO, "Merge MS: %s\n", old_ms_name); if (strlen(ms_imsi(ms)) == 0 && strlen(ms_imsi(old_ms)) != 0) osmo_strlcpy(ms->imsi, ms_imsi(old_ms), sizeof(ms->imsi)); @@ -406,36 +474,42 @@ static void ms_merge_old_ms(struct GprsMs *ms, struct GprsMs *old_ms) if (!ms_egprs_ms_class(ms) && ms_egprs_ms_class(old_ms)) ms_set_egprs_ms_class(ms, ms_egprs_ms_class(old_ms)); + + if ((dl_tbf = ms_dl_tbf(old_ms))) { + /* Move the last partially/totally unacked LLC PDU back to the LLC queue: */ + dl_tbf_copy_unacked_pdus_to_llc_queue(dl_tbf); + } + /* Now merge the old_ms queue into the new one: */ llc_queue_move_and_merge(&ms->llc_queue, &old_ms->llc_queue); + /* Clean up the old MS object */ ms_reset(old_ms); + + ms_unref(old_ms, __func__); } -void ms_merge_and_clear_ms(struct GprsMs *ms, struct GprsMs *old_ms) +/* Apply changes to the TLLI directly, used interally by functions below: */ +static void ms_apply_tlli_change(struct GprsMs *ms, uint32_t tlli) { - OSMO_ASSERT(old_ms != ms); - - ms_ref(old_ms); - - /* Clean up the old MS object */ - /* TODO: Use timer? */ - if (ms_ul_tbf(old_ms) && !tbf_timers_pending((struct gprs_rlcmac_tbf *)ms_ul_tbf(old_ms), T_MAX)) - tbf_free((struct gprs_rlcmac_tbf *)ms_ul_tbf(old_ms)); - if (ms_dl_tbf(old_ms) && !tbf_timers_pending((struct gprs_rlcmac_tbf *)ms_dl_tbf(old_ms), T_MAX)) - tbf_free((struct gprs_rlcmac_tbf *)ms_dl_tbf(old_ms)); - - ms_merge_old_ms(ms, old_ms); + ms->tlli = tlli; + ms->new_dl_tlli = GSM_RESERVED_TMSI; + ms->new_ul_tlli = GSM_RESERVED_TMSI; - ms_unref(old_ms); + /* Update TBF FSM names: */ + if (ms->ul_tbf) + tbf_update_state_fsm_name(ul_tbf_as_tbf(ms->ul_tbf)); + if (ms->dl_tbf) + tbf_update_state_fsm_name(dl_tbf_as_tbf(ms->dl_tbf)); } +/* Set/update the MS object TLLI based on knowledge gained from the MS side (Uplink direction) */ void ms_set_tlli(struct GprsMs *ms, uint32_t tlli) { if (tlli == ms->tlli || tlli == ms->new_ul_tlli) return; if (tlli != ms->new_dl_tlli) { - LOGP(DRLCMAC, LOGL_INFO, + LOGP(DMS, LOGL_INFO, "Modifying MS object, UL TLLI: 0x%08x -> 0x%08x, " "not yet confirmed\n", ms_tlli(ms), tlli); @@ -443,16 +517,15 @@ void ms_set_tlli(struct GprsMs *ms, uint32_t tlli) return; } - LOGP(DRLCMAC, LOGL_INFO, + LOGP(DMS, LOGL_INFO, "Modifying MS object, TLLI: 0x%08x -> 0x%08x, " "already confirmed partly\n", ms->tlli, tlli); - ms->tlli = tlli; - ms->new_dl_tlli = GSM_RESERVED_TMSI; - ms->new_ul_tlli = GSM_RESERVED_TMSI; + ms_apply_tlli_change(ms, tlli); } +/* Set/update the MS object TLLI based on knowledge gained from the SGSN side (Downlink direction) */ bool ms_confirm_tlli(struct GprsMs *ms, uint32_t tlli) { if (tlli == ms->tlli || tlli == ms->new_dl_tlli) @@ -462,7 +535,7 @@ bool ms_confirm_tlli(struct GprsMs *ms, uint32_t tlli) /* The MS has not sent a message with the new TLLI, which may * happen according to the spec [TODO: add reference]. */ - LOGP(DRLCMAC, LOGL_INFO, + LOGP(DMS, LOGL_INFO, "The MS object cannot fully confirm an unexpected TLLI: 0x%08x, " "partly confirmed\n", tlli); /* Use the network's idea of TLLI as candidate, this does not @@ -471,12 +544,10 @@ bool ms_confirm_tlli(struct GprsMs *ms, uint32_t tlli) return false; } - LOGP(DRLCMAC, LOGL_INFO, + LOGP(DMS, LOGL_INFO, "Modifying MS object, TLLI: 0x%08x confirmed\n", tlli); - ms->tlli = tlli; - ms->new_dl_tlli = GSM_RESERVED_TMSI; - ms->new_ul_tlli = GSM_RESERVED_TMSI; + ms_apply_tlli_change(ms, tlli); return true; } @@ -484,12 +555,12 @@ bool ms_confirm_tlli(struct GprsMs *ms, uint32_t tlli) void ms_set_imsi(struct GprsMs *ms, const char *imsi) { if (!imsi) { - LOGP(DRLCMAC, LOGL_ERROR, "Expected IMSI!\n"); + LOGP(DMS, LOGL_ERROR, "Expected IMSI!\n"); return; } if (imsi[0] && strlen(imsi) < 3) { - LOGP(DRLCMAC, LOGL_ERROR, "No valid IMSI '%s'!\n", + LOGP(DMS, LOGL_ERROR, "No valid IMSI '%s'!\n", imsi); return; } @@ -497,11 +568,11 @@ void ms_set_imsi(struct GprsMs *ms, const char *imsi) if (strcmp(imsi, ms->imsi) == 0) return; - LOGP(DRLCMAC, LOGL_INFO, + LOGP(DMS, LOGL_INFO, "Modifying MS object, TLLI = 0x%08x, IMSI '%s' -> '%s'\n", ms_tlli(ms), ms->imsi, imsi); - struct GprsMs *old_ms = bts_ms_by_imsi(ms->bts, imsi); + struct GprsMs *old_ms = bts_get_ms_by_imsi(ms->bts, imsi); /* Check if we are going to store a different MS object with already existing IMSI. This is probably a bug in code calling this function, since it should take care of this explicitly */ @@ -510,16 +581,23 @@ void ms_set_imsi(struct GprsMs *ms, const char *imsi) * different IMSI */ OSMO_ASSERT(old_ms != ms); - LOGPMS(ms, DRLCMAC, LOGL_NOTICE, + LOGPMS(ms, DMS, LOGL_NOTICE, "IMSI '%s' was already assigned to another " "MS object: TLLI = 0x%08x, that IMSI will be removed\n", imsi, ms_tlli(old_ms)); ms_merge_and_clear_ms(ms, old_ms); + /* old_ms may no longer be available here */ } - + /* Store the new IMSI: */ osmo_strlcpy(ms->imsi, imsi, sizeof(ms->imsi)); + + /* Update TBF FSM names: */ + if (ms->ul_tbf) + tbf_update_state_fsm_name(ul_tbf_as_tbf(ms->ul_tbf)); + if (ms->dl_tbf) + tbf_update_state_fsm_name(dl_tbf_as_tbf(ms->dl_tbf)); } void ms_set_ta(struct GprsMs *ms, uint8_t ta_) @@ -528,12 +606,12 @@ void ms_set_ta(struct GprsMs *ms, uint8_t ta_) return; if (gsm48_ta_is_valid(ta_)) { - LOGP(DRLCMAC, LOGL_INFO, + LOGP(DMS, LOGL_INFO, "Modifying MS object, TLLI = 0x%08x, TA %d -> %d\n", ms_tlli(ms), ms->ta, ta_); ms->ta = ta_; } else - LOGP(DRLCMAC, LOGL_NOTICE, + LOGP(DMS, LOGL_NOTICE, "MS object, TLLI = 0x%08x, invalid TA %d rejected (old " "value %d kept)\n", ms_tlli(ms), ta_, ms->ta); } @@ -543,7 +621,7 @@ void ms_set_ms_class(struct GprsMs *ms, uint8_t ms_class_) if (ms_class_ == ms->ms_class) return; - LOGP(DRLCMAC, LOGL_INFO, + LOGP(DMS, LOGL_INFO, "Modifying MS object, TLLI = 0x%08x, MS class %d -> %d\n", ms_tlli(ms), ms->ms_class, ms_class_); @@ -555,14 +633,14 @@ void ms_set_egprs_ms_class(struct GprsMs *ms, uint8_t ms_class_) if (ms_class_ == ms->egprs_ms_class) return; - LOGP(DRLCMAC, LOGL_INFO, + LOGP(DMS, LOGL_INFO, "Modifying MS object, TLLI = 0x%08x, EGPRS MS class %d -> %d\n", ms_tlli(ms), ms->egprs_ms_class, ms_class_); ms->egprs_ms_class = ms_class_; if (!bts_max_mcs_ul(ms->bts) || !bts_max_mcs_dl(ms->bts)) { - LOGPMS(ms, DRLCMAC, LOGL_DEBUG, + LOGPMS(ms, DMS, LOGL_DEBUG, "Avoid enabling EGPRS because use of MCS is disabled: ul=%u dl=%u\n", bts_max_mcs_ul(ms->bts), bts_max_mcs_dl(ms->bts)); return; @@ -576,7 +654,7 @@ void ms_set_egprs_ms_class(struct GprsMs *ms, uint8_t ms_class_) } else { ms_set_mode(ms, EGPRS); } - LOGPMS(ms, DRLCMAC, LOGL_INFO, "Enabled EGPRS, mode %s\n", mode_name(ms_mode(ms))); + LOGPMS(ms, DMS, LOGL_INFO, "Enabled EGPRS, mode %s\n", mode_name(ms_mode(ms))); } void ms_update_error_rate(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf, int error_rate) @@ -815,7 +893,7 @@ enum CodingScheme ms_current_cs_dl(const struct GprsMs *ms, enum mcs_kind req_mc } if (orig_cs != cs) - LOGPMS(ms, DRLCMACDL, LOGL_INFO, "MS (mode=%s) suggests transmitting " + LOGPMS(ms, DMS, LOGL_INFO, "MS (mode=%s) suggests transmitting " "DL %s, downgrade to %s in order to match TBF & scheduler requirements\n", mode_name(ms_mode(ms)), mcs_name(orig_cs), mcs_name(cs)); @@ -823,7 +901,7 @@ enum CodingScheme ms_current_cs_dl(const struct GprsMs *ms, enum mcs_kind req_mc /* If the DL TBF is active, add number of unencoded chunk octets */ if (ms->dl_tbf) - unencoded_octets += llc_chunk_size(tbf_llc((struct gprs_rlcmac_tbf *)ms->dl_tbf)); + unencoded_octets += llc_chunk_size(tbf_llc(dl_tbf_as_tbf(ms->dl_tbf))); /* There are many unencoded octets, don't reduce */ if (unencoded_octets >= the_pcu->vty.cs_downgrade_threshold) @@ -843,15 +921,15 @@ enum CodingScheme ms_current_cs_dl(const struct GprsMs *ms, enum mcs_kind req_mc return cs; } -int ms_first_common_ts(const struct GprsMs *ms) +struct gprs_rlcmac_pdch *ms_first_common_ts(const struct GprsMs *ms) { - if (ms->dl_tbf) - return tbf_first_common_ts((struct gprs_rlcmac_tbf *)ms->dl_tbf); - - if (ms->ul_tbf) - return tbf_first_common_ts((struct gprs_rlcmac_tbf *)ms->ul_tbf); + return ms->first_common_ts; +} - return -1; +void ms_set_first_common_ts(struct GprsMs *ms, struct gprs_rlcmac_pdch *pdch) +{ + OSMO_ASSERT(pdch); + ms->first_common_ts = pdch; } uint8_t ms_dl_slots(const struct GprsMs *ms) @@ -859,10 +937,10 @@ uint8_t ms_dl_slots(const struct GprsMs *ms) uint8_t slots = 0; if (ms->dl_tbf) - slots |= tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf); + slots |= tbf_dl_slots(dl_tbf_as_tbf(ms->dl_tbf)); if (ms->ul_tbf) - slots |= tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf); + slots |= tbf_dl_slots(ul_tbf_as_tbf(ms->ul_tbf)); return slots; } @@ -872,10 +950,10 @@ uint8_t ms_ul_slots(const struct GprsMs *ms) uint8_t slots = 0; if (ms->dl_tbf) - slots |= tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf); + slots |= tbf_ul_slots(dl_tbf_as_tbf(ms->dl_tbf)); if (ms->ul_tbf) - slots |= tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf); + slots |= tbf_ul_slots(ul_tbf_as_tbf(ms->ul_tbf)); return slots; } @@ -884,20 +962,20 @@ uint8_t ms_current_pacch_slots(const struct GprsMs *ms) { uint8_t slots = 0; - bool is_dl_active = ms->dl_tbf && tbf_is_tfi_assigned((struct gprs_rlcmac_tbf *)ms->dl_tbf); - bool is_ul_active = ms->ul_tbf && tbf_is_tfi_assigned((struct gprs_rlcmac_tbf *)ms->ul_tbf); + bool is_dl_active = ms->dl_tbf && tbf_is_tfi_assigned(dl_tbf_as_tbf(ms->dl_tbf)); + bool is_ul_active = ms->ul_tbf && tbf_is_tfi_assigned(ul_tbf_as_tbf(ms->ul_tbf)); if (!is_dl_active && !is_ul_active) return 0; /* see TS 44.060, 8.1.1.2.2 */ if (is_dl_active && !is_ul_active) - slots = tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf); + slots = tbf_dl_slots(dl_tbf_as_tbf(ms->dl_tbf)); else if (!is_dl_active && is_ul_active) - slots = tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf); + slots = tbf_ul_slots(ul_tbf_as_tbf(ms->ul_tbf)); else - slots = tbf_ul_slots((struct gprs_rlcmac_tbf *)ms->ul_tbf) & - tbf_dl_slots((struct gprs_rlcmac_tbf *)ms->dl_tbf); + slots = tbf_ul_slots(ul_tbf_as_tbf(ms->ul_tbf)) & + tbf_dl_slots(dl_tbf_as_tbf(ms->dl_tbf)); /* Assume a multislot class 1 device */ /* TODO: For class 2 devices, this could be removed */ @@ -931,13 +1009,40 @@ void ms_set_reserved_slots(struct GprsMs *ms, struct gprs_rlcmac_trx *trx, struct gprs_rlcmac_tbf *ms_tbf(const struct GprsMs *ms, enum gprs_rlcmac_tbf_direction dir) { switch (dir) { - case GPRS_RLCMAC_DL_TBF: return (struct gprs_rlcmac_tbf *)ms->dl_tbf; - case GPRS_RLCMAC_UL_TBF: return (struct gprs_rlcmac_tbf *)ms->ul_tbf; + case GPRS_RLCMAC_DL_TBF: return dl_tbf_as_tbf(ms->dl_tbf); + case GPRS_RLCMAC_UL_TBF: return ul_tbf_as_tbf(ms->ul_tbf); } return NULL; } +const char *ms_name(const struct GprsMs *ms) +{ + static char _ms_name_buf[128]; + return ms_name_buf(ms, _ms_name_buf, sizeof(_ms_name_buf)); +} + +char *ms_name_buf(const struct GprsMs *ms, char *buf, unsigned int buf_size) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buf_size }; + uint32_t tlli = ms_tlli(ms); + + OSMO_STRBUF_PRINTF(sb, "MS("); + if (ms_imsi_is_valid(ms)) + OSMO_STRBUF_PRINTF(sb, "IMSI-%s:", ms_imsi(ms)); + if (tlli != GSM_RESERVED_TMSI) + OSMO_STRBUF_PRINTF(sb, "TLLI-0x%08x:", tlli); + OSMO_STRBUF_PRINTF(sb, "TA-%" PRIu8 ":MSCLS-%" PRIu8 "-%" PRIu8, + ms_ta(ms), ms_ms_class(ms), ms_egprs_ms_class(ms)); + if (ms->ul_tbf) + OSMO_STRBUF_PRINTF(sb, ":UL"); + if (ms->dl_tbf) + OSMO_STRBUF_PRINTF(sb, ":DL"); + + OSMO_STRBUF_PRINTF(sb, ")"); + return buf; +} + int ms_nacc_start(struct GprsMs *ms, Packet_Cell_Change_Notification_t *notif) { if (!ms->nacc) @@ -957,15 +1062,16 @@ bool ms_nacc_rts(const struct GprsMs *ms) return false; } -struct msgb *ms_nacc_create_rlcmac_msg(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf, uint32_t fn, uint8_t ts) +struct msgb *ms_nacc_create_rlcmac_msg(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf, + const struct gprs_rlcmac_pdch *pdch, uint32_t fn) { int rc; struct nacc_ev_create_rlcmac_msg_ctx data_ctx; data_ctx = (struct nacc_ev_create_rlcmac_msg_ctx) { .tbf = tbf, + .pdch = pdch, .fn = fn, - .ts = ts, .msg = NULL, }; @@ -974,3 +1080,331 @@ struct msgb *ms_nacc_create_rlcmac_msg(struct GprsMs *ms, struct gprs_rlcmac_tbf return NULL; return data_ctx.msg; } + +static void ms_start_llc_timer(struct GprsMs *ms) +{ + if (the_pcu->vty.llc_idle_ack_csec > 0) { + struct timespec tv; + csecs_to_timespec(the_pcu->vty.llc_idle_ack_csec, &tv); + osmo_timer_schedule(&ms->llc_timer, tv.tv_sec, tv.tv_nsec / 1000); + } +} + +/* Can we get to send a DL TBF ass to the MS? */ +static bool ms_is_reachable_for_dl_ass(const struct GprsMs *ms) +{ + const struct gprs_rlcmac_dl_tbf *dl_tbf = ms_dl_tbf(ms); + const struct gprs_rlcmac_ul_tbf *ul_tbf = ms_ul_tbf(ms); + + /* This function assumes it is called when no DL TBF is present, or + * alternatively if it's not really in use by the MS (TBF_ST_WAIT_REUSE_TFI) */ + OSMO_ASSERT(!dl_tbf || + tbf_state(dl_tbf_as_tbf_const(dl_tbf)) == TBF_ST_WAIT_REUSE_TFI); + + /* 3GPP TS 44.060 sec 7.1.3.1 Initiation of the Packet resource request procedure: + * "Furthermore, the mobile station shall not respond to PACKET DOWNLINK ASSIGNMENT + * or MULTIPLE TBF DOWNLINK ASSIGNMENT messages before contention resolution is + * completed on the mobile station side." */ + /* The possible uplink TBF is used to trigger downlink assignment: + * - If there is no uplink TBF the MS is potentially in packet idle mode + * and hence assignment will be done over CCCH (PCH) + * - If there's an uplink TBF but it is finished (waiting for last PKT + * CTRL ACK after sending last Pkt UL ACK/NACK with FINAL_ACK=1, then we + * have no ways to contact the MS right now. Assignment will be delayed + * until PKT CTRL ACK is received and the TBF is released at the MS side + * (then assignment goes through PCH). + */ + if (!ul_tbf) + return true; + if (ul_tbf_contention_resolution_done(ul_tbf) && + !tbf_ul_ack_waiting_cnf_final_ack(ul_tbf) && + tbf_state(ul_tbf_as_tbf_const(ul_tbf)) != TBF_ST_RELEASING) + return true; + + return false; + +} + +/* Alloc a UL TBF to be assigned over PACCH. Called when an MS requests to + * create a new UL TBF during the end of life of a previous UL TBF (or an SBA). + * In summary, this TBF is allocated as a consequence of receiving a "Pkt + * Resource Req" or "Pkt Ctrl Ack" from the MS. + * See TS 44.060 9.3.2.4.2 "Non-extended uplink TBF mode". + */ +struct gprs_rlcmac_ul_tbf *ms_new_ul_tbf_assigned_pacch(struct GprsMs *ms, int8_t use_trx) +{ + struct gprs_rlcmac_ul_tbf *ul_tbf; + const struct alloc_resources_req req = { + .bts = ms->bts, + .ms = ms, + .direction = GPRS_RLCMAC_UL_TBF, + .single = false, + .use_trx = use_trx, + }; + struct alloc_resources_res res = {}; + int rc; + + rc = the_pcu->alloc_algorithm(&req, &res); + if (rc < 0) { + LOGPMS(ms, DTBF, LOGL_NOTICE, + "Timeslot Allocation failed: trx = %d, single_slot = %d\n", + req.use_trx, req.single); + bts_do_rate_ctr_inc(ms->bts, CTR_TBF_ALLOC_FAIL); + return NULL; + } + + ul_tbf = ul_tbf_alloc(ms->bts, ms); + if (!ul_tbf) { + LOGPMS(ms, DTBF, LOGL_NOTICE, "ul_tbf_alloc() failed\n"); + /* Caller will most probably send a Imm Ass Reject after return */ + return NULL; + } + + /* Update MS, really allocate the resources */ + if (res.reserved_ul_slots != ms_reserved_ul_slots(ms) || + res.reserved_dl_slots != ms_reserved_dl_slots(ms)) { + /* The reserved slots have changed, update the MS */ + ms_set_reserved_slots(ms, res.trx, res.reserved_ul_slots, res.reserved_dl_slots); + } + ms_set_first_common_ts(ms, res.first_common_ts); + + /* Apply allocated resources to TBF: */ + ul_tbf_apply_allocated_resources(ul_tbf, &res); + + ms_attach_tbf(ms, ul_tbf_as_tbf(ul_tbf)); + + osmo_fsm_inst_dispatch(tbf_state_fi(ul_tbf_as_tbf(ul_tbf)), TBF_EV_ASSIGN_ADD_PACCH, NULL); + /* Contention resolution is considered to be done since TLLI is known in MS */ + return ul_tbf; +} + +/* Alloc a UL TBF to be assigned over AGCH. Used by request of a "One phase + * packet access", where MS requested only 1 PDCH TS (TS 44.018 Table 9.1.8.1). */ +struct gprs_rlcmac_ul_tbf *ms_new_ul_tbf_assigned_agch(struct GprsMs *ms) +{ + struct gprs_rlcmac_ul_tbf *ul_tbf; + const struct alloc_resources_req req = { + .bts = ms->bts, + .ms = ms, + .direction = GPRS_RLCMAC_UL_TBF, + .single = true, + .use_trx = -1, + }; + struct alloc_resources_res res = {}; + int rc; + + rc = the_pcu->alloc_algorithm(&req, &res); + if (rc < 0) { + LOGPMS(ms, DTBF, LOGL_NOTICE, + "Timeslot Allocation failed: trx = %d, single_slot = %d\n", + req.use_trx, req.single); + bts_do_rate_ctr_inc(ms->bts, CTR_TBF_ALLOC_FAIL); + return NULL; + } + + ul_tbf = ul_tbf_alloc(ms->bts, ms); + if (!ul_tbf) { + LOGPMS(ms, DTBF, LOGL_NOTICE, "ul_tbf_alloc() failed\n"); + /* Caller will most probably send a Imm Ass Reject after return */ + return NULL; + } + + /* Update MS, really allocate the resources */ + if (res.reserved_ul_slots != ms_reserved_ul_slots(ms) || + res.reserved_dl_slots != ms_reserved_dl_slots(ms)) { + /* The reserved slots have changed, update the MS */ + ms_set_reserved_slots(ms, res.trx, res.reserved_ul_slots, res.reserved_dl_slots); + } + ms_set_first_common_ts(ms, res.first_common_ts); + + /* Apply allocated resources to TBF: */ + ul_tbf_apply_allocated_resources(ul_tbf, &res); + + ms_attach_tbf(ms, ul_tbf_as_tbf(ul_tbf)); + + osmo_fsm_inst_dispatch(tbf_state_fi(ul_tbf_as_tbf(ul_tbf)), TBF_EV_ASSIGN_ADD_CCCH, NULL); + return ul_tbf; +} + +/* Create a temporary dummy TBF to Tx a ImmAssReject if allocating a new one during + * packet resource Request failed. This is similar as ul_tbf_alloc() but without + * calling alloc_algo (in charge of TFI/USF allocation), and reusing resources + * from Packet Resource Request we received. See TS 44.060 sec 7.1.3.2.1 */ +struct gprs_rlcmac_ul_tbf *ms_new_ul_tbf_rejected_pacch(struct GprsMs *ms, struct gprs_rlcmac_pdch *pdch) +{ + struct gprs_rlcmac_ul_tbf *ul_tbf; + struct alloc_resources_res fake_res = { + .trx = pdch->trx, + .first_common_ts = pdch, + .reserved_ul_slots = 0, + .reserved_dl_slots = 0, + .ass_slots_mask = 0, + .upgrade_to_multislot = false, + .tfi = TBF_TFI_UNSET, + .usf = {0}, + }; + ul_tbf = ul_tbf_alloc(ms->bts, ms); + if (!ul_tbf) + return NULL; + + /* The only one TS is the common, control TS */ + ms_set_first_common_ts(ms, pdch); + + /* Apply fake resources to TBF, to attach it to the proper TRX/PDCH: */ + ul_tbf_apply_allocated_resources(ul_tbf, &fake_res); + + ms_attach_tbf(ms, ul_tbf_as_tbf(ul_tbf)); + + osmo_fsm_inst_dispatch(tbf_state_fi(ul_tbf_as_tbf(ul_tbf)), TBF_EV_ASSIGN_ADD_PACCH, NULL); + osmo_fsm_inst_dispatch(tbf_ul_ass_fi(ul_tbf_as_tbf(ul_tbf)), TBF_UL_ASS_EV_SCHED_ASS_REJ, NULL); + + return ul_tbf; +} + +/* A new DL-TBF is allocated and assigned through PACCH using "tbf". + * "tbf" may be either a UL-TBF or a DL-TBF. + * Note: This should be called only when MS is reachable, see ms_is_reachable_for_dl_ass(). + */ +int ms_new_dl_tbf_assigned_on_pacch(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf) +{ + OSMO_ASSERT(tbf); + struct gprs_rlcmac_dl_tbf *dl_tbf; + const struct alloc_resources_req req = { + .bts = ms->bts, + .ms = ms, + .direction = GPRS_RLCMAC_DL_TBF, + .single = false, + .use_trx = tbf_get_trx(tbf)->trx_no, + }; + struct alloc_resources_res res = {}; + int rc; + + rc = the_pcu->alloc_algorithm(&req, &res); + if (rc < 0) { + LOGPMS(ms, DTBF, LOGL_NOTICE, + "Timeslot Allocation failed: trx = %d, single_slot = %d\n", + req.use_trx, req.single); + bts_do_rate_ctr_inc(ms->bts, CTR_TBF_ALLOC_FAIL); + return -EBUSY; + } + + dl_tbf = dl_tbf_alloc(ms->bts, ms); + if (!dl_tbf) { + LOGPMS(ms, DTBF, LOGL_NOTICE, "dl_tbf_alloc() failed\n"); + return -1; + } + + /* Update MS, really allocate the resources */ + if (res.reserved_ul_slots != ms_reserved_ul_slots(ms) || + res.reserved_dl_slots != ms_reserved_dl_slots(ms)) { + /* The reserved slots have changed, update the MS */ + ms_set_reserved_slots(ms, res.trx, res.reserved_ul_slots, res.reserved_dl_slots); + } + ms_set_first_common_ts(ms, res.first_common_ts); + + /* Apply allocated resources to TBF: */ + dl_tbf_apply_allocated_resources(dl_tbf, &res); + + ms_attach_tbf(ms, dl_tbf_as_tbf(dl_tbf)); + + LOGPTBFDL(dl_tbf, LOGL_DEBUG, "[DOWNLINK] START (PACCH)\n"); + dl_tbf_trigger_ass_on_pacch(dl_tbf, tbf); + return 0; +} + +/* A new DL-TBF is allocated and assigned through PCH. + * Note: This should be called only when MS is reachable, see ms_is_reachable_for_dl_ass(). + */ +int ms_new_dl_tbf_assigned_on_pch(struct GprsMs *ms) +{ + struct gprs_rlcmac_dl_tbf *dl_tbf; + const struct alloc_resources_req req = { + .bts = ms->bts, + .ms = ms, + .direction = GPRS_RLCMAC_DL_TBF, + .single = true, + .use_trx = -1, + }; + struct alloc_resources_res res = {}; + int rc; + + rc = the_pcu->alloc_algorithm(&req, &res); + if (rc < 0) { + LOGPMS(ms, DTBF, LOGL_NOTICE, + "Timeslot Allocation failed: trx = %d, single_slot = %d\n", + req.use_trx, req.single); + bts_do_rate_ctr_inc(ms->bts, CTR_TBF_ALLOC_FAIL); + return -EBUSY; + } + + dl_tbf = dl_tbf_alloc(ms->bts, ms); + if (!dl_tbf) { + LOGPMS(ms, DTBF, LOGL_NOTICE, "dl_tbf_alloc() failed\n"); + return -1; + } + + /* Update MS, really allocate the resources */ + if (res.reserved_ul_slots != ms_reserved_ul_slots(ms) || + res.reserved_dl_slots != ms_reserved_dl_slots(ms)) { + /* The reserved slots have changed, update the MS */ + ms_set_reserved_slots(ms, res.trx, res.reserved_ul_slots, res.reserved_dl_slots); + } + ms_set_first_common_ts(ms, res.first_common_ts); + + /* Apply allocated resources to TBF: */ + dl_tbf_apply_allocated_resources(dl_tbf, &res); + + ms_attach_tbf(ms, dl_tbf_as_tbf(dl_tbf)); + + LOGPTBFDL(dl_tbf, LOGL_DEBUG, "[DOWNLINK] START (PCH)\n"); + dl_tbf_trigger_ass_on_pch(dl_tbf); + return 0; +} + +int ms_append_llc_dl_data(struct GprsMs *ms, uint16_t pdu_delay_csec, const uint8_t *data, uint16_t len) +{ + struct timespec expire_time; + struct gprs_rlcmac_dl_tbf *dl_tbf; + int rc = 0; + + LOGPMS(ms, DTBFDL, LOGL_DEBUG, "appending %u bytes to DL LLC queue\n", len); + + struct msgb *llc_msg = msgb_alloc(len, "llc_pdu_queue"); + if (!llc_msg) + return -ENOMEM; + + llc_queue_calc_pdu_lifetime(ms->bts, pdu_delay_csec, &expire_time); + memcpy(msgb_put(llc_msg, len), data, len); + llc_queue_enqueue(ms_llc_queue(ms), llc_msg, &expire_time); + ms_start_llc_timer(ms); + + dl_tbf = ms_dl_tbf(ms); + if (dl_tbf) { + switch (tbf_state(dl_tbf_as_tbf_const(dl_tbf))) { + case TBF_ST_WAIT_RELEASE: + LOGPTBFDL(dl_tbf, LOGL_DEBUG, "in WAIT RELEASE state (T3192), so reuse TBF\n"); + rc = ms_new_dl_tbf_assigned_on_pacch(ms, dl_tbf_as_tbf(dl_tbf)); + return rc; + case TBF_ST_WAIT_REUSE_TFI: + /* According to DL TBF state it should be back to CCCH, let's check UL TBF to have more information. */ + break; + case TBF_ST_RELEASING: + /* Something went wrong (T3195), delay for later. */ + default: + /* DL TBF in working status (do nothing)*/ + return 0; + } + + } + + /* Check if we can create a DL TBF to start sending the enqueued + * data. Otherwise it will be triggered later when it is reachable + * again. */ + if (ms_is_reachable_for_dl_ass(ms)) { + if (ms_ul_tbf(ms)) + rc = ms_new_dl_tbf_assigned_on_pacch(ms, ul_tbf_as_tbf(ms_ul_tbf(ms))); + else + rc = ms_new_dl_tbf_assigned_on_pch(ms); + } + return rc; +} diff --git a/src/gprs_ms.h b/src/gprs_ms.h index 4438a4c9..276733ca 100644 --- a/src/gprs_ms.h +++ b/src/gprs_ms.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -35,6 +31,7 @@ extern "C" { #include <osmocom/core/timer.h> #include <osmocom/core/linuxlist.h> #include <osmocom/core/rate_ctr.h> +#include <osmocom/core/use_count.h> #include <osmocom/gsm/protocol/gsm_23_003.h> #include <osmocom/gsm/gsm48.h> @@ -54,20 +51,16 @@ struct gprs_rlcmac_bts; struct gprs_rlcmac_trx; struct GprsMs; -struct gpr_ms_callback { - void (*ms_idle)(struct GprsMs *); - void (*ms_active)(struct GprsMs *); -}; - struct GprsMs { struct llist_head list; /* list of all GprsMs */ - struct gpr_ms_callback cb; bool app_info_pending; struct gprs_rlcmac_bts *bts; struct gprs_rlcmac_ul_tbf *ul_tbf; struct gprs_rlcmac_dl_tbf *dl_tbf; struct llist_head old_tbfs; /* list of gprs_rlcmac_tbf */ + /* First common timeslot used both in UL and DL, or NULL if not set: */ + struct gprs_rlcmac_pdch *first_common_ts; uint32_t tlli; uint32_t new_ul_tlli; @@ -83,11 +76,12 @@ struct GprsMs { enum CodingScheme current_cs_dl; struct gprs_llc_queue llc_queue; + struct osmo_timer_list llc_timer; - bool is_idle; - int ref; - struct osmo_timer_list timer; - unsigned delay; + struct osmo_use_count use_count; + struct osmo_timer_list release_timer; + /* Time at which MS became idle and waiting to be released by release_timer: */ + struct timeval tv_idle_start; int64_t last_cs_not_low; @@ -96,21 +90,18 @@ struct GprsMs { uint8_t reserved_dl_slots; uint8_t reserved_ul_slots; struct gprs_rlcmac_trx *current_trx; - - struct gprs_codel *codel_state; enum mcs_kind mode; struct rate_ctr_group *ctrs; struct nacc_fsm_ctx *nacc; }; -struct GprsMs *ms_alloc(struct gprs_rlcmac_bts *bts, uint32_t tlli); +struct GprsMs *ms_alloc(struct gprs_rlcmac_bts *bts, const char *use_ref); -int ms_first_common_ts(const struct GprsMs *ms); +struct gprs_rlcmac_pdch *ms_first_common_ts(const struct GprsMs *ms); +void ms_set_first_common_ts(struct GprsMs *ms, struct gprs_rlcmac_pdch *pdch); void ms_set_reserved_slots(struct GprsMs *ms, struct gprs_rlcmac_trx *trx, uint8_t ul_slots, uint8_t dl_slots); -struct GprsMs *ms_ref(struct GprsMs *ms); -void ms_unref(struct GprsMs *ms); void ms_set_mode(struct GprsMs *ms, enum mcs_kind mode); void ms_set_ms_class(struct GprsMs *ms, uint8_t ms_class_); void ms_set_egprs_ms_class(struct GprsMs *ms, uint8_t ms_class_); @@ -124,6 +115,7 @@ void ms_set_current_cs_dl(struct GprsMs *ms, enum CodingScheme scheme); void ms_update_error_rate(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf, int error_rate); uint8_t ms_current_pacch_slots(const struct GprsMs *ms); +void ms_update_announced_tlli(struct GprsMs *ms, uint32_t tlli); void ms_merge_and_clear_ms(struct GprsMs *ms, struct GprsMs *old_ms); void ms_attach_tbf(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf); @@ -139,16 +131,25 @@ struct gprs_rlcmac_tbf *ms_tbf(const struct GprsMs *ms, enum gprs_rlcmac_tbf_dir static inline struct gprs_rlcmac_ul_tbf *ms_ul_tbf(const struct GprsMs *ms) {return ms->ul_tbf;} static inline struct gprs_rlcmac_dl_tbf *ms_dl_tbf(const struct GprsMs *ms) {return ms->dl_tbf;} - -void ms_set_callback(struct GprsMs *ms, struct gpr_ms_callback *cb); +const char *ms_name(const struct GprsMs *ms); +char *ms_name_buf(const struct GprsMs *ms, char *buf, unsigned int buf_size); int ms_nacc_start(struct GprsMs *ms, Packet_Cell_Change_Notification_t *notif); bool ms_nacc_rts(const struct GprsMs *ms); -struct msgb *ms_nacc_create_rlcmac_msg(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf, uint32_t fn, uint8_t ts); +struct msgb *ms_nacc_create_rlcmac_msg(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf, + const struct gprs_rlcmac_pdch *pdch, uint32_t fn); + +struct gprs_rlcmac_ul_tbf *ms_new_ul_tbf_assigned_pacch(struct GprsMs *ms, int8_t use_trx); +struct gprs_rlcmac_ul_tbf *ms_new_ul_tbf_assigned_agch(struct GprsMs *ms); +struct gprs_rlcmac_ul_tbf *ms_new_ul_tbf_rejected_pacch(struct GprsMs *ms, struct gprs_rlcmac_pdch *pdch); +int ms_new_dl_tbf_assigned_on_pacch(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf); +int ms_new_dl_tbf_assigned_on_pch(struct GprsMs *ms); +int ms_append_llc_dl_data(struct GprsMs *ms, uint16_t pdu_delay_csec, const uint8_t *data, uint16_t len); static inline bool ms_is_idle(const struct GprsMs *ms) { - return !ms->ul_tbf && !ms->dl_tbf && !ms->ref && llist_empty(&ms->old_tbfs); + return !ms->ul_tbf && !ms->dl_tbf && + llist_empty(&ms->old_tbfs); } static inline struct gprs_llc_queue *ms_llc_queue(struct GprsMs *ms) @@ -156,11 +157,30 @@ static inline struct gprs_llc_queue *ms_llc_queue(struct GprsMs *ms) return &ms->llc_queue; } +/* Function used in code where a ul_tbf related event occurs and hence its state + * changes, which in turn may change the entire MS state (eg becoming reachable + * over PCH or PACCH) and the caller may want to know if it is a good time to + * assign a DL TBF (and whether we have DL data to send) */ static inline bool ms_need_dl_tbf(struct GprsMs *ms) { - if (ms_dl_tbf(ms) != NULL && - tbf_state((const struct gprs_rlcmac_tbf *)ms_dl_tbf(ms)) != TBF_ST_WAIT_RELEASE) - return false; + struct gprs_rlcmac_dl_tbf *dl_tbf = ms_dl_tbf(ms); + if (dl_tbf) { + switch (tbf_state(dl_tbf_as_tbf(dl_tbf))) { + case TBF_ST_NEW: + case TBF_ST_ASSIGN: + case TBF_ST_FLOW: + case TBF_ST_FINISHED: + case TBF_ST_WAIT_RELEASE: + return false; /* TBF in use, hence no need for new DL TBF */ + case TBF_ST_WAIT_REUSE_TFI: + case TBF_ST_RELEASING: + /* TBF cannot be used to send further data, a new one + * may be needed, check below */ + break; + default: + OSMO_ASSERT(0); + } + } return llc_queue_size(ms_llc_queue(ms)) > 0; } @@ -186,6 +206,11 @@ static inline const char *ms_imsi(const struct GprsMs *ms) return ms->imsi; } +static inline bool ms_imsi_is_valid(const struct GprsMs *ms) +{ + return ms->imsi[0] != '\0'; +} + static inline uint8_t ms_ta(const struct GprsMs *ms) { return ms->ta; @@ -211,16 +236,6 @@ static inline enum mcs_kind ms_mode(const struct GprsMs *ms) return ms->mode; } -static inline void ms_set_timeout(struct GprsMs *ms, unsigned secs) -{ - ms->delay = secs; -} - -static inline struct gprs_codel *ms_codel_state(const struct GprsMs *ms) -{ - return ms->codel_state; -} - static inline unsigned ms_nack_rate_dl(const struct GprsMs *ms) { return ms->nack_rate_dl; @@ -241,12 +256,14 @@ static inline struct gprs_rlcmac_trx *ms_current_trx(const struct GprsMs *ms) return ms->current_trx; } +#define MS_USE_TBF "tbf" +#define ms_ref(ms, use) \ + OSMO_ASSERT(osmo_use_count_get_put(&(ms)->use_count, use, 1) == 0) +#define ms_unref(ms, use) \ + OSMO_ASSERT(osmo_use_count_get_put(&(ms)->use_count, use, -1) == 0) + #define LOGPMS(ms, category, level, fmt, args...) \ - LOGP(category, level, "MS(TLLI=0x%08x, IMSI=%s, TA=%" PRIu8 ", %" PRIu8 "/%" PRIu8 ",%s%s) " fmt, \ - ms_tlli(ms), ms_imsi(ms), ms_ta(ms), ms_ms_class(ms), ms_egprs_ms_class(ms), \ - ms_ul_tbf(ms) ? " UL": "", \ - ms_dl_tbf(ms) ? " DL": "", \ - ## args) + LOGP(category, level, "%s " fmt, ms_name(ms), ## args) #ifdef __cplusplus } diff --git a/src/gprs_ms_storage.cpp b/src/gprs_ms_storage.cpp deleted file mode 100644 index 6245ed9f..00000000 --- a/src/gprs_ms_storage.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* gprs_ms_storage.cpp - * - * Copyright (C) 2015 by Sysmocom s.f.m.c. GmbH - * Author: Jacob Erlbeck <jerlbeck@sysmocom.de> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - - -#include "gprs_ms_storage.h" - -#include "tbf.h" -#include "bts.h" - -extern "C" { - #include <osmocom/core/linuxlist.h> - #include <osmocom/gsm/gsm48.h> -} - -#define GPRS_UNDEFINED_IMSI "000" - -static void ms_storage_ms_idle_cb(struct GprsMs *ms) -{ - llist_del(&ms->list); - if (ms->bts) - bts_stat_item_add(ms->bts, STAT_MS_PRESENT, -1); - if (ms_is_idle(ms)) - talloc_free(ms); -} - -static void ms_storage_ms_active_cb(struct GprsMs *ms) -{ - /* Nothing to do */ -} - -static struct gpr_ms_callback ms_storage_ms_cb = { - .ms_idle = ms_storage_ms_idle_cb, - .ms_active = ms_storage_ms_active_cb, -}; - -GprsMsStorage::GprsMsStorage(struct gprs_rlcmac_bts *bts) : - m_bts(bts) -{ - INIT_LLIST_HEAD(&m_list); -} - -GprsMsStorage::~GprsMsStorage() -{ - cleanup(); -} - -void GprsMsStorage::cleanup() -{ - struct llist_head *pos, *tmp; - - llist_for_each_safe(pos, tmp, &m_list) { - struct GprsMs *ms = llist_entry(pos, typeof(*ms), list); - ms_set_callback(ms, NULL); - ms_storage_ms_idle_cb(ms); - } -} - -GprsMs *GprsMsStorage::get_ms(uint32_t tlli, uint32_t old_tlli, const char *imsi) const -{ - struct llist_head *tmp; - GprsMs *ms; - - if (tlli != GSM_RESERVED_TMSI || old_tlli != GSM_RESERVED_TMSI) { - llist_for_each(tmp, &m_list) { - ms = llist_entry(tmp, typeof(*ms), list); - if (ms_check_tlli(ms, tlli)) - return ms; - if (ms_check_tlli(ms, old_tlli)) - return ms; - } - } - - /* not found by TLLI */ - - if (imsi && imsi[0] && strcmp(imsi, GPRS_UNDEFINED_IMSI) != 0) { - llist_for_each(tmp, &m_list) { - ms = llist_entry(tmp, typeof(*ms), list); - if (strcmp(imsi, ms_imsi(ms)) == 0) - return ms; - } - } - - return NULL; -} - -GprsMs *GprsMsStorage::create_ms() -{ - GprsMs *ms; - - ms = ms_alloc(m_bts, GSM_RESERVED_TMSI); - - ms_set_callback(ms, &ms_storage_ms_cb); - llist_add(&ms->list, &m_list); - if (m_bts) - bts_stat_item_add(m_bts, STAT_MS_PRESENT, 1); - - return ms; -} diff --git a/src/gprs_ms_storage.h b/src/gprs_ms_storage.h deleted file mode 100644 index dcb6d8df..00000000 --- a/src/gprs_ms_storage.h +++ /dev/null @@ -1,44 +0,0 @@ -/* gprs_ms_storage.h - * - * Copyright (C) 2015 by Sysmocom s.f.m.c. GmbH - * Author: Jacob Erlbeck <jerlbeck@sysmocom.de> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#pragma once - -#include "gprs_ms.h" -#include "tbf.h" -#include <stdint.h> -#include <stddef.h> - -struct gprs_rlcmac_bts; - -struct GprsMsStorage { -public: - GprsMsStorage(struct gprs_rlcmac_bts *bts); - ~GprsMsStorage(); - - void cleanup(); - - GprsMs *get_ms(uint32_t tlli, uint32_t old_tlli = GSM_RESERVED_TMSI, const char *imsi = NULL) const; - GprsMs *create_ms(); - - const struct llist_head* ms_list() const {return &m_list;} -private: - struct gprs_rlcmac_bts *m_bts; - struct llist_head m_list; /* list of struct GprsMs */ -}; diff --git a/src/gprs_pcu.c b/src/gprs_pcu.c index 5ed9d7d6..2c2454bb 100644 --- a/src/gprs_pcu.c +++ b/src/gprs_pcu.c @@ -6,8 +6,8 @@ * 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 + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, @@ -15,7 +15,7 @@ * 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 + * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ @@ -33,22 +33,39 @@ static struct osmo_tdef T_defs_pcu[] = { { .T=3113, .default_val=7, .unit=OSMO_TDEF_S, .desc="Timeout for paging", .val=0 }, { .T=3190, .default_val=5, .unit=OSMO_TDEF_S, .desc="Return to packet idle mode after Packet DL Assignment on CCCH (s)", .val=0}, { .T=3141, .default_val=10, .unit=OSMO_TDEF_S, .desc="Timeout for contention resolution procedure (s)", .val=0 }, + { .T=3172, .default_val=5000, .unit=OSMO_TDEF_MS, .desc="Wait Indication used in Imm Ass Reject during TBF Establishment (PACCH)", .val=0, .min_val = 0, .max_val = 255000 }, /* TS 44.060 7.1.3.2.1 */ { .T=PCU_TDEF_NEIGH_RESOLVE_TO, .default_val=1000, .unit=OSMO_TDEF_MS, .desc="[ARFCN+BSIC]->[RAC+CI] resolution timeout (ms)", .val=0 }, { .T=PCU_TDEF_SI_RESOLVE_TO, .default_val=1000, .unit=OSMO_TDEF_MS, .desc="RIM RAN-INFO response timeout (ms)", .val=0 }, { .T=PCU_TDEF_NEIGH_CACHE_ALIVE, .default_val=5, .unit=OSMO_TDEF_S, .desc="[ARFCN+BSIC]->[RAC+CI] resolution cache entry storage timeout (s)", .val=0 }, { .T=PCU_TDEF_SI_CACHE_ALIVE, .default_val=5, .unit=OSMO_TDEF_S, .desc="[RAC+CI]->[SI] resolution cache entry storage timeout (s)", .val=0 }, { .T=-101, .default_val=30, .unit=OSMO_TDEF_S, .desc="BSSGP (un)blocking procedures timer (s)", .val=0 }, { .T=-102, .default_val=30, .unit=OSMO_TDEF_S, .desc="BSSGP reset procedure timer (s)", .val=0 }, - { .T=-2000, .default_val=2, .unit=OSMO_TDEF_MS, .desc="Delay release of UL TBF after tx Packet Access Reject (PACCH) (ms)", .val=0 }, - { .T=-2001, .default_val=2, .unit=OSMO_TDEF_S, .desc="PACCH assignment timeout (s)", .val=0 }, + { .T=-2000, .default_val=0, .unit=OSMO_TDEF_MS, .desc="Delay release of UL TBF after tx Packet Access Reject (PACCH) (ms)", .val=0 }, + { .T=-2001, .default_val=2, .unit=OSMO_TDEF_S, .desc="DL TBF PACCH assignment timeout (s)", .val=0 }, { .T=-2002, .default_val=200, .unit=OSMO_TDEF_MS, .desc="Waiting after IMM.ASS confirm timer (ms)", .val=0 }, { .T=-2030, .default_val=60, .unit=OSMO_TDEF_S, .desc="Time to keep an idle MS object alive (s)", .val=0 }, /* slightly above T3314 (default 44s, 24.008, 11.2.2) */ { .T=-2031, .default_val=2000, .unit=OSMO_TDEF_MS, .desc="Time to keep an idle DL TBF alive (ms)", .val=0 }, { .T=0, .default_val=0, .unit=OSMO_TDEF_S, .desc=NULL, .val=0 } /* empty item at the end */ }; +static void _update_stats_timer_cb(void *data) +{ + struct gprs_pcu *pcu = (struct gprs_pcu *)data; + struct gprs_rlcmac_bts *bts; + + llist_for_each_entry(bts, &pcu->bts_list, list) + osmo_time_cc_set_flag(&bts->all_allocated_pdch, bts_all_pdch_allocated(bts)); + + osmo_timer_schedule(&pcu->update_stats_timer, 1, 0); +} + static int gprs_pcu_talloc_destructor(struct gprs_pcu *pcu) { + struct gprs_rlcmac_bts *bts; + while ((bts = llist_first_entry_or_null(&pcu->bts_list, struct gprs_rlcmac_bts, list))) + talloc_free(bts); + + osmo_timer_del(&pcu->update_stats_timer); neigh_cache_free(pcu->neigh_cache); si_cache_free(pcu->si_cache); return 0; @@ -112,10 +129,9 @@ struct gprs_pcu *gprs_pcu_alloc(void *ctx) /* TODO: increase them when CRBB decoding is implemented */ pcu->vty.ws_base = 64; pcu->vty.ws_pdch = 0; + pcu->vty.msclass_default = PCU_DEFAULT_MSLOT_CLASS; pcu->vty.llc_codel_interval_msec = LLC_CODEL_USE_DEFAULT; pcu->vty.llc_idle_ack_csec = 10; - pcu->vty.neigh_ctrl_addr = NULL; /* don't use CTRL iface for Neigh Addr Resolution */ - pcu->vty.neigh_ctrl_port = OSMO_CTRL_PORT_BSC_NEIGH; pcu->T_defs = T_defs_pcu; osmo_tdefs_reset(pcu->T_defs); @@ -125,6 +141,9 @@ struct gprs_pcu *gprs_pcu_alloc(void *ctx) pcu->neigh_cache = neigh_cache_alloc(pcu, osmo_tdef_get(pcu->T_defs, PCU_TDEF_NEIGH_CACHE_ALIVE, OSMO_TDEF_S, -1)); pcu->si_cache = si_cache_alloc(pcu, osmo_tdef_get(pcu->T_defs, PCU_TDEF_SI_CACHE_ALIVE, OSMO_TDEF_S, -1)); + osmo_timer_setup(&pcu->update_stats_timer, _update_stats_timer_cb, pcu); + osmo_timer_schedule(&pcu->update_stats_timer, 1, 0); + return pcu; } diff --git a/src/gprs_pcu.h b/src/gprs_pcu.h index 86fe8eb4..c789d914 100644 --- a/src/gprs_pcu.h +++ b/src/gprs_pcu.h @@ -6,8 +6,8 @@ * 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 + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, @@ -15,7 +15,7 @@ * 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 + * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ @@ -25,6 +25,7 @@ #include <stdbool.h> #include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/timer.h> #include "gprs_bssgp_pcu.h" #include "coding_scheme.h" @@ -42,6 +43,9 @@ #define PCU_TDEF_NEIGH_CACHE_ALIVE (-10) #define PCU_TDEF_SI_CACHE_ALIVE (-11) +/* If Multislot Class is not known during TS allocation, assume ms_class=12: Rx=4 Tx=4 Sum=5 */ +#define PCU_DEFAULT_MSLOT_CLASS 12 + /* see bts->gsmtap_categ_mask */ enum pcu_gsmtap_category { PCU_GSMTAP_C_DL_UNKNOWN = 0, /* unknown or undecodable downlink blocks */ @@ -65,10 +69,11 @@ enum pcu_gsmtap_category { struct gprs_rlcmac_bts; struct GprsMs; struct gprs_rlcmac_tbf; +struct alloc_resources_req; +struct alloc_resources_res; -typedef int (*alloc_algorithm_func_t)(struct gprs_rlcmac_bts *bts, - struct gprs_rlcmac_tbf *tbf, - bool single, int8_t use_tbf); +typedef int (*alloc_algorithm_func_t)(const struct alloc_resources_req *req, + struct alloc_resources_res *res); struct gprs_pcu { @@ -106,19 +111,18 @@ struct gprs_pcu { int ns_priority; uint16_t ws_base; uint16_t ws_pdch; /* increase WS by this value per PDCH */ + uint8_t msclass_default; /* Assume this MSCLASS if unknown when creating TBF */ uint16_t force_llc_lifetime; /* overrides lifetime from SGSN */ uint32_t llc_discard_csec; uint32_t llc_idle_ack_csec; uint32_t llc_codel_interval_msec; /* 0=disabled, -1=use default interval */ - /* Remote BSS resolution sevice (CTRL iface) */ - char *neigh_ctrl_addr; - uint16_t neigh_ctrl_port; } vty; struct gsmtap_inst *gsmtap; uint32_t gsmtap_categ_mask; + char *gsmtap_remote_host; - struct llist_head bts_list; /* list of gprs_rlcmac_tbf */ + struct llist_head bts_list; /* list of gprs_rlcmac_bts */ struct gprs_ns2_inst *nsi; @@ -130,6 +134,10 @@ struct gprs_pcu { struct neigh_cache *neigh_cache; /* ARFC+BSIC -> CGI PS cache */ struct si_cache *si_cache; /* ARFC+BSIC -> CGI PS cache */ + + struct osmo_timer_list update_stats_timer; /* Used to update some time_cc stats periodically */ + + uint8_t pcu_if_version; }; diff --git a/src/gprs_rlcmac.cpp b/src/gprs_rlcmac.c index 22b12dfc..b2cbecaa 100644 --- a/src/gprs_rlcmac.cpp +++ b/src/gprs_rlcmac.c @@ -1,8 +1,9 @@ -/* gprs_rlcmac.cpp +/* gprs_rlcmac.c * * Copyright (C) 2012 Ivan Klyuchnikov * Copyright (C) 2012 Andreas Eversberg <jolly@eversberg.eu> * Copyright (C) 2013 by Holger Hans Peter Freyther + * Copyright (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -13,20 +14,13 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -extern "C" { - #include <osmocom/gsm/gsm48.h> -} +#include <osmocom/gsm/gsm48.h> #include <pcu_l1_if.h> #include <gprs_rlcmac.h> #include <bts.h> -#include <bts_pch_timer.h> #include <encoding.h> #include <tbf.h> #include <gprs_debug.h> @@ -34,23 +28,23 @@ extern "C" { extern void *tall_pcu_ctx; int gprs_rlcmac_paging_request(struct gprs_rlcmac_bts *bts, const struct osmo_mobile_identity *mi, - uint16_t pgroup) + const char *imsi) { if (log_check_level(DRLCMAC, LOGL_NOTICE)) { char str[64]; osmo_mobile_identity_to_str_buf(str, sizeof(str), mi); LOGP(DRLCMAC, LOGL_NOTICE, "TX: [PCU -> BTS] Paging Request (CCCH) MI=%s\n", str); } - bitvec *paging_request = bitvec_alloc(22, tall_pcu_ctx); + struct bitvec *paging_request = bitvec_alloc(22, tall_pcu_ctx); bitvec_unhex(paging_request, DUMMY_VEC); - int plen = Encoding::write_paging_request(paging_request, mi); + int plen = write_paging_request(paging_request, mi); if (plen <= 0) { LOGP(DRLCMAC, LOGL_ERROR, "TX: [PCU -> BTS] Failed to encode Paging Request\n"); return -1; } bts_do_rate_ctr_inc(bts, CTR_PCH_REQUESTS); - bts_pch_timer_start(bts, mi->imsi); - pcu_l1if_tx_pch(bts, paging_request, plen, pgroup); + + pcu_l1if_tx_pch2(bts, paging_request, plen, false, imsi, GSM_RESERVED_TMSI); bitvec_free(paging_request); return 0; diff --git a/src/gprs_rlcmac.h b/src/gprs_rlcmac.h index cf95c37f..70cf63e9 100644 --- a/src/gprs_rlcmac.h +++ b/src/gprs_rlcmac.h @@ -2,6 +2,7 @@ * * Copyright (C) 2012 Ivan Klyuchnikov * Copyright (C) 2013 by Holger Hans Peter Freyther + * Copyright (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -12,14 +13,9 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifndef GPRS_RLCMAC_H -#define GPRS_RLCMAC_H +#pragma once #include <stdbool.h> #include <stdint.h> @@ -96,28 +92,14 @@ enum gprs_rlcmac_block_type { int gprs_rlcmac_tx_ul_ud(gprs_rlcmac_tbf *tbf); -struct msgb *gprs_rlcmac_app_info_msg(const struct gsm_pcu_if_app_info_req *req); - int gprs_rlcmac_rcv_rts_block(struct gprs_rlcmac_bts *bts, uint8_t trx, uint8_t ts, uint32_t fn, uint8_t block_nr); extern "C" { #endif -int alloc_algorithm_a(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, bool single, - int8_t use_trx); - -int alloc_algorithm_b(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, bool single, - int8_t use_trx); - -int alloc_algorithm_dynamic(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf, bool single, - int8_t use_trx); - -int gprs_rlcmac_paging_request(struct gprs_rlcmac_bts *bts, const struct osmo_mobile_identity *mi, uint16_t pgroup); -int gprs_alloc_max_dl_slots_per_ms(const struct gprs_rlcmac_bts *bts, uint8_t ms_class); +int gprs_rlcmac_paging_request(struct gprs_rlcmac_bts *bts, const struct osmo_mobile_identity *mi, const char *imsi); +struct msgb *gprs_rlcmac_app_info_msg(const struct gsm_pcu_if_app_info_req *req); #ifdef __cplusplus } #endif - - -#endif // GPRS_RLCMAC_H diff --git a/src/gprs_rlcmac_meas.cpp b/src/gprs_rlcmac_meas.cpp index b9a324fb..a3d891cc 100644 --- a/src/gprs_rlcmac_meas.cpp +++ b/src/gprs_rlcmac_meas.cpp @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ extern "C" { @@ -128,8 +124,8 @@ int gprs_rlcmac_received_lost(struct gprs_rlcmac_dl_tbf *tbf, uint16_t received, if (!sum) return -EINVAL; - LOGP(DRLCMACMEAS, LOGL_DEBUG, "DL Loss of TLLI 0x%08x: Received: %4d " - "Lost: %4d Sum: %4d\n", tbf->tlli(), received, lost, sum); + LOGP(DRLCMACMEAS, LOGL_DEBUG, "%s DL Loss: Received: %4d Lost: %4d Sum: %4d\n", + tbf_name(dl_tbf_as_tbf_const(tbf)), received, lost, sum); tbf->m_bw.dl_loss_received += received; tbf->m_bw.dl_loss_lost += lost; @@ -158,9 +154,8 @@ int gprs_rlcmac_lost_rep(struct gprs_rlcmac_dl_tbf *tbf) if (!sum) return -EINVAL; - LOGP(DRLCMACMEAS, LOGL_DEBUG, "DL packet loss of IMSI=%s / TLLI=0x%08x: " - "%d%%\n", tbf->imsi(), tbf->tlli(), - tbf->m_bw.dl_loss_lost * 100 / sum); + LOGP(DRLCMACMEAS, LOGL_DEBUG, "%s DL packet loss: %d%%\n", + tbf_name(dl_tbf_as_tbf_const(tbf)), tbf->m_bw.dl_loss_lost * 100 / sum); return 0; } @@ -183,8 +178,8 @@ int gprs_rlcmac_dl_bw(struct gprs_rlcmac_dl_tbf *tbf, uint16_t octets) return 0; tbf->m_bw.dl_throughput = (tbf->m_bw.dl_bw_octets << 10) / ((elapsed.tv_sec << 10) + (elapsed.tv_nsec >> 20)); - LOGP(DRLCMACMEAS, LOGL_INFO, "DL Bandwitdh of IMSI=%s / TLLI=0x%08x: " - "%d KBits/s\n", tbf->imsi(), tbf->tlli(), tbf->m_bw.dl_throughput); + LOGP(DRLCMACMEAS, LOGL_INFO, "%s DL Bandwitdh: %d KBits/s\n", + tbf_name(dl_tbf_as_tbf_const(tbf)), tbf->m_bw.dl_throughput); /* reset bandwidth values timestamp */ memcpy(bw_tv, &now_tv, sizeof(*bw_tv)); diff --git a/src/gprs_rlcmac_sched.cpp b/src/gprs_rlcmac_sched.cpp index 8867d436..ac581216 100644 --- a/src/gprs_rlcmac_sched.cpp +++ b/src/gprs_rlcmac_sched.cpp @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <gprs_bssgp_pcu.h> @@ -32,6 +28,7 @@ extern "C" { #include <osmocom/core/gsmtap.h> + #include "nacc_fsm.h" } struct tbf_sched_candidates { @@ -49,35 +46,27 @@ static void get_ctrl_msg_tbf_candidates(const struct gprs_rlcmac_pdch *pdch, struct llist_item *pos; llist_for_each_entry(pos, &pdch->trx->ul_tbfs, list) { - ul_tbf = as_ul_tbf((struct gprs_rlcmac_tbf *)pos->entry); + ul_tbf = tbf_as_ul_tbf((struct gprs_rlcmac_tbf *)pos->entry); OSMO_ASSERT(ul_tbf); - /* this trx, this ts */ - if (!ul_tbf->is_control_ts(pdch->ts_no)) - continue; - if (tbf_ul_ack_rts(ul_tbf)) + if (tbf_ul_ack_rts(ul_tbf, pdch)) tbf_cand->ul_ack = ul_tbf; - if (tbf_dl_ass_rts(ul_tbf)) + if (tbf_dl_ass_rts(ul_tbf, pdch)) tbf_cand->dl_ass = ul_tbf; - if (tbf_ul_ass_rts(ul_tbf)) + if (tbf_ul_ass_rts(ul_tbf, pdch)) tbf_cand->ul_ass = ul_tbf; - /* NACC ready to send. TFI assigned is needed to send messages */ - if (ul_tbf->is_tfi_assigned() && ms_nacc_rts(ul_tbf->ms())) + if (tbf_nacc_rts(ul_tbf, pdch)) tbf_cand->nacc = ul_tbf; /* FIXME: Is this supposed to be fair? The last TBF for each wins? Maybe use llist_add_tail and skip once we have all states? */ } llist_for_each_entry(pos, &pdch->trx->dl_tbfs, list) { - dl_tbf = as_dl_tbf((struct gprs_rlcmac_tbf *)pos->entry); + dl_tbf = tbf_as_dl_tbf((struct gprs_rlcmac_tbf *)pos->entry); OSMO_ASSERT(dl_tbf); - /* this trx, this ts */ - if (!dl_tbf->is_control_ts(pdch->ts_no)) - continue; - if (tbf_dl_ass_rts(dl_tbf)) + if (tbf_dl_ass_rts(dl_tbf, pdch)) tbf_cand->dl_ass = dl_tbf; - if (tbf_ul_ass_rts(dl_tbf)) + if (tbf_ul_ass_rts(dl_tbf, pdch)) tbf_cand->ul_ass = dl_tbf; - /* NACC ready to send. TFI assigned is needed to send messages */ - if (dl_tbf->is_tfi_assigned() && ms_nacc_rts(dl_tbf->ms())) + if (tbf_nacc_rts(dl_tbf, pdch)) tbf_cand->nacc = dl_tbf; } } @@ -150,7 +139,6 @@ static struct msgb *sched_select_ctrl_msg(struct gprs_rlcmac_pdch *pdch, uint32_ tbfs->dl_ass, tbfs->ul_ack, tbfs->nacc }; - uint8_t ts = pdch->ts_no; /* Send Packet Application Information first (ETWS primary notifications) */ msg = sched_app_info(tbfs->dl_ass); @@ -167,16 +155,15 @@ static struct msgb *sched_select_ctrl_msg(struct gprs_rlcmac_pdch *pdch, uint32_ * received, thus preventing the others from being processed. */ if (tbf == tbfs->ul_ass && tbf->ul_ass_state_is(TBF_UL_ASS_SEND_ASS_REJ)) - msg = tbf_ul_ass_create_rlcmac_msg(tbfs->ul_ass, fn, ts); + msg = tbf_ul_ass_create_rlcmac_msg(tbfs->ul_ass, pdch, fn); else if (tbf == tbfs->ul_ass && tbf->direction == GPRS_RLCMAC_DL_TBF) - msg = tbf_ul_ass_create_rlcmac_msg(tbfs->ul_ass, fn, ts); + msg = tbf_ul_ass_create_rlcmac_msg(tbfs->ul_ass, pdch, fn); else if (tbf == tbfs->dl_ass && tbf->direction == GPRS_RLCMAC_UL_TBF) - msg = tbf_dl_ass_create_rlcmac_msg(tbfs->dl_ass, fn, ts); + msg = tbf_dl_ass_create_rlcmac_msg(tbfs->dl_ass, pdch, fn); else if (tbf == tbfs->ul_ack) - msg = tbf_ul_ack_create_rlcmac_msg(tbfs->ul_ack, fn, ts); - else if (tbf == tbfs->nacc) { - msg = ms_nacc_create_rlcmac_msg(tbf->ms(), tbf, fn, ts); - } + msg = tbf_ul_ack_create_rlcmac_msg(tbfs->ul_ack, pdch, fn); + else if (tbf == tbfs->nacc) + msg = ms_nacc_create_rlcmac_msg(tbf->ms(), tbf, pdch, fn); if (!msg) { tbf = NULL; @@ -196,10 +183,10 @@ static struct msgb *sched_select_ctrl_msg(struct gprs_rlcmac_pdch *pdch, uint32_ */ if (tbfs->dl_ass) { tbf = tbfs->dl_ass; - msg = tbf_dl_ass_create_rlcmac_msg(tbfs->dl_ass, fn, ts); + msg = tbf_dl_ass_create_rlcmac_msg(tbfs->dl_ass, pdch, fn); } else if (tbfs->ul_ass) { tbf = tbfs->ul_ass; - msg = tbf_ul_ass_create_rlcmac_msg(tbfs->ul_ass, fn, ts); + msg = tbf_ul_ass_create_rlcmac_msg(tbfs->ul_ass, pdch, fn); } } @@ -231,24 +218,25 @@ static struct msgb *sched_select_ctrl_msg(struct gprs_rlcmac_pdch *pdch, uint32_ } static inline enum tbf_dl_prio tbf_compute_priority(const struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_dl_tbf *tbf, - uint8_t ts, uint32_t fn, int age) + const struct gprs_rlcmac_pdch *pdch, uint32_t fn, int age) { const gprs_rlc_dl_window *w = static_cast<gprs_rlc_dl_window *>(tbf->window()); unsigned long msecs_t3190 = osmo_tdef_get(the_pcu->T_defs, 3190, OSMO_TDEF_MS, -1); unsigned long dl_tbf_idle_msec = osmo_tdef_get(the_pcu->T_defs, -2031, OSMO_TDEF_MS, -1); int age_thresh1 = msecs_to_frames(200); int age_thresh2 = msecs_to_frames(OSMO_MIN(msecs_t3190/2, dl_tbf_idle_msec)); + const bool is_control_ts = tbf_is_control_ts(tbf, pdch); - if (tbf->is_control_ts(ts) && tbf->need_poll_for_dl_ack_nack()) + if (is_control_ts && tbf->need_poll_for_dl_ack_nack()) return DL_PRIO_CONTROL; - if (tbf->is_control_ts(ts) && age > age_thresh2 && age_thresh2 > 0) + if (is_control_ts && age > age_thresh2 && age_thresh2 > 0) return DL_PRIO_HIGH_AGE; if ((tbf->state_is(TBF_ST_FLOW) && tbf->have_data()) || w->resend_needed() >= 0) return DL_PRIO_NEW_DATA; - if (tbf->is_control_ts(ts) && age > age_thresh1 && tbf->keep_open(fn)) + if (is_control_ts && age > age_thresh1 && tbf->keep_open(fn)) return DL_PRIO_LOW_AGE; if (!w->window_empty()) @@ -289,7 +277,6 @@ static struct msgb *sched_select_dl_data_msg(struct gprs_rlcmac_bts *bts, struct struct msgb *msg = NULL; struct gprs_rlcmac_dl_tbf *tbf, *prio_tbf = NULL; enum tbf_dl_prio prio, max_prio = DL_PRIO_NONE; - uint8_t ts = pdch->ts_no; uint8_t i, tfi, prio_tfi; int age; @@ -316,7 +303,7 @@ static struct msgb *sched_select_dl_data_msg(struct gprs_rlcmac_bts *bts, struct age = tbf->frames_since_last_poll(fn); /* compute priority */ - prio = tbf_compute_priority(bts, tbf, ts, fn, age); + prio = tbf_compute_priority(bts, tbf, pdch, fn, age); if (prio == DL_PRIO_NONE) continue; @@ -345,7 +332,7 @@ static struct msgb *sched_select_dl_data_msg(struct gprs_rlcmac_bts *bts, struct /* next TBF to handle resource is the next one */ pdch->next_dl_tfi = (prio_tfi + 1) & 31; /* generate DL data block */ - msg = prio_tbf->create_dl_acked_block(fn, ts, req_mcs_kind); + msg = prio_tbf->create_dl_acked_block(fn, pdch, req_mcs_kind); *is_egprs = prio_tbf->is_egprs_enabled(); } @@ -460,8 +447,8 @@ int gprs_rlcmac_rcv_rts_block(struct gprs_rlcmac_bts *bts, * let's set its USF in the DL msg. This is not really needed, * but it helps understand better the flow when looking at * pcaps. */ - if (poll_tbf->direction == GPRS_RLCMAC_UL_TBF && as_ul_tbf(poll_tbf)->m_usf[ts] != USF_INVALID) - usf_tbf = as_ul_tbf(poll_tbf); + if (poll_tbf->direction == GPRS_RLCMAC_UL_TBF && tbf_as_ul_tbf(poll_tbf)->m_usf[ts] != USF_INVALID) + usf_tbf = tbf_as_ul_tbf(poll_tbf); /* else, search for uplink tbf */ } else if ((usf_tbf = sched_select_uplink(pdch, require_gprs_only))) { LOGPDCH(pdch, DRLCMACSCHED, LOGL_DEBUG, "Received RTS for PDCH: FN=%d " @@ -478,7 +465,7 @@ int gprs_rlcmac_rcv_rts_block(struct gprs_rlcmac_bts *bts, /* Prio 1: select control message */ if ((msg = sched_select_ctrl_msg(pdch, fn, block_nr, &tbf_cand))) { - gsmtap_cat = PCU_GSMTAP_C_DL_CTRL; + gsmtap_cat = PCU_GSMTAP_C_DL_CTRL; } /* Prio 2: select data message for downlink */ else if((msg = sched_select_dl_data_msg(bts, pdch, fn, block_nr, req_mcs_kind, &tx_is_egprs))) { @@ -500,11 +487,10 @@ int gprs_rlcmac_rcv_rts_block(struct gprs_rlcmac_bts *bts, const unsigned num_tbfs = pdch->num_tbfs(GPRS_RLCMAC_DL_TBF) + pdch->num_tbfs(GPRS_RLCMAC_UL_TBF); bool skip_idle = (num_tbfs == 0); -#ifdef ENABLE_DIRECT_PHY - /* In DIRECT_PHY mode we want to always submit something to L1 in - * TRX0, since BTS is not preparing dummy bursts on idle TS for us */ - skip_idle = skip_idle && trx != 0; -#endif + + if (bts->gen_idle_blocks_C0) + skip_idle = skip_idle && trx != 0; + if (!skip_idle && (msg = sched_dummy())) { /* increase counter */ gsmtap_cat = PCU_GSMTAP_C_DL_DUMMY; diff --git a/src/gsm_rlcmac.c b/src/gsm_rlcmac.c index 6793602c..570c97b8 100644 --- a/src/gsm_rlcmac.c +++ b/src/gsm_rlcmac.c @@ -23,10 +23,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <osmocom/core/utils.h> diff --git a/src/gsm_rlcmac.h b/src/gsm_rlcmac.h index 01e2ee63..c43d4181 100644 --- a/src/gsm_rlcmac.h +++ b/src/gsm_rlcmac.h @@ -23,10 +23,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef __PACKET_GSM_RLCMAC_H__ diff --git a/src/llc.c b/src/llc.c new file mode 100644 index 00000000..45b8ac49 --- /dev/null +++ b/src/llc.c @@ -0,0 +1,404 @@ +/* Copied from tbf.cpp + * + * Copyright (C) 2012 Ivan Klyuchnikov + * Copyright (C) 2012 Andreas Eversberg <jolly@eversberg.eu> + * Copyright (C) 2013 by Holger Hans Peter Freyther + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include <stdio.h> + +#include <osmocom/core/msgb.h> + +#include "bts.h" +#include "gprs_ms.h" +#include "pcu_utils.h" +#include "llc.h" + +void llc_init(struct gprs_llc *llc) +{ + llc_reset(llc); +} + +/* reset LLC frame */ +void llc_reset(struct gprs_llc *llc) +{ + llc->index = 0; + llc->length = 0; + llc->prio = 0; + llc->meta_info = (struct MetaInfo){0}; + + memset(llc->frame, 0x42, sizeof(llc->frame)); +} + +void llc_reset_frame_space(struct gprs_llc *llc) +{ + llc->index = 0; +} + +/* Put an Unconfirmed Information (UI) Dummy command, see GSM 44.064, 6.4.2.2 */ +void llc_put_dummy_frame(struct gprs_llc *llc, size_t req_len) +{ + /* The shortest dummy command (the spec requests at least 6 octets) */ + static const uint8_t llc_dummy_command[] = { + 0x43, 0xc0, 0x01, 0x2b, 0x2b, 0x2b + }; + static const size_t max_dummy_command_len = 79; + + llc_put_frame(llc, llc_dummy_command, sizeof(llc_dummy_command)); + + if (req_len > max_dummy_command_len) + req_len = max_dummy_command_len; + + /* Add further stuffing, if the requested length exceeds the minimum + * dummy command length */ + if (llc->length < req_len) { + memset(&llc->frame[llc->length], 0x2b, req_len - llc->length); + llc->length = req_len; + } +} + +void llc_put_frame(struct gprs_llc *llc, const uint8_t *data, size_t len) +{ + /* only put frames when we are empty */ + OSMO_ASSERT(llc->index == 0 && llc->length == 0); + llc_append_frame(llc, data, len); +} + +void llc_append_frame(struct gprs_llc *llc, const uint8_t *data, size_t len) +{ + /* TODO: bounds check */ + memcpy(llc->frame + llc->length, data, len); + llc->length += len; +} + +static bool llc_pdu_can_be_discarded(const uint8_t *data, size_t len) +{ + const unsigned keep_small_thresh = 60; + + /* Is the frame small, perhaps only a TCP ACK? */ + if (len <= keep_small_thresh) + return false; + + if ((data[0] & 0x0f) == 1 /* GPRS_SAPI_GMM */) + return false; + + if ((data[0] & 0xe0) != 0xc0 /* LLC UI */) + /* It is not an LLC UI frame, see TS 44.064, 6.3 */ + return false; + + return true; +} + +void llc_queue_init(struct gprs_llc_queue *q, struct GprsMs *ms) +{ + unsigned int i; + + q->ms = ms; + q->queue_size = 0; + q->queue_octets = 0; + q->avg_queue_delay = 0; + for (i = 0; i < ARRAY_SIZE(q->pq); i++) { + INIT_LLIST_HEAD(&q->pq[i].queue); + gprs_codel_init(&q->pq[i].codel_state); + } +} + +/* interval=0 -> don't use codel in the LLC queue */ +void llc_queue_set_codel_interval(struct gprs_llc_queue *q, unsigned int interval) +{ + unsigned int i; + if (interval == LLC_CODEL_DISABLE) { + q->use_codel = false; + return; + } + q->use_codel = true; + for (i = 0; i < ARRAY_SIZE(q->pq); i++) + gprs_codel_set_interval(&q->pq[i].codel_state, interval); +} + +static enum gprs_llc_queue_prio llc_sapi2prio(uint8_t sapi) +{ + switch (sapi) { + case 1: + return LLC_QUEUE_PRIO_GMM; + case 2: + case 7: + case 8: + return LLC_QUEUE_PRIO_TOM_SMS; + default: + return LLC_QUEUE_PRIO_OTHER; + } +} + +void llc_queue_enqueue(struct gprs_llc_queue *q, struct msgb *llc_msg, const struct timespec *expire_time) +{ + struct MetaInfo *meta_storage; + struct gprs_llc_hdr *llc_hdr = (struct gprs_llc_hdr *)msgb_data(llc_msg); + enum gprs_llc_queue_prio prio; + + osmo_static_assert(sizeof(*meta_storage) <= sizeof(llc_msg->cb), info_does_not_fit); + + prio = llc_sapi2prio(llc_hdr->sapi); + + q->queue_size += 1; + q->queue_octets += msgb_length(llc_msg); + + meta_storage = (struct MetaInfo *)&llc_msg->cb[0]; + osmo_clock_gettime(CLOCK_MONOTONIC, &meta_storage->recv_time); + meta_storage->expire_time = *expire_time; + + msgb_enqueue(&q->pq[prio].queue, llc_msg); +} + +void llc_queue_clear(struct gprs_llc_queue *q, struct gprs_rlcmac_bts *bts) +{ + struct msgb *msg; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(q->pq); i++) { + while ((msg = msgb_dequeue(&q->pq[i].queue))) { + if (bts) + bts_do_rate_ctr_inc(bts, CTR_LLC_FRAME_DROPPED); + msgb_free(msg); + } + } + + q->queue_size = 0; + q->queue_octets = 0; +} + +void llc_queue_move_and_merge(struct gprs_llc_queue *q, struct gprs_llc_queue *o) +{ + struct msgb *msg, *msg1 = NULL, *msg2 = NULL; + struct llist_head new_queue; + unsigned int i; + size_t queue_size = 0; + size_t queue_octets = 0; + INIT_LLIST_HEAD(&new_queue); + + for (i = 0; i < ARRAY_SIZE(q->pq); i++) { + while (1) { + if (msg1 == NULL) + msg1 = msgb_dequeue(&q->pq[i].queue); + + if (msg2 == NULL) + msg2 = msgb_dequeue(&o->pq[i].queue); + + if (msg1 == NULL && msg2 == NULL) + break; + + if (msg1 == NULL) { + msg = msg2; + msg2 = NULL; + } else if (msg2 == NULL) { + msg = msg1; + msg1 = NULL; + } else { + const struct MetaInfo *mi1 = (struct MetaInfo *)&msg1->cb[0]; + const struct MetaInfo *mi2 = (struct MetaInfo *)&msg2->cb[0]; + + if (timespeccmp(&mi2->recv_time, &mi1->recv_time, >)) { + msg = msg1; + msg1 = NULL; + } else { + msg = msg2; + msg2 = NULL; + } + } + + msgb_enqueue(&new_queue, msg); + queue_size += 1; + queue_octets += msgb_length(msg); + } + + OSMO_ASSERT(llist_empty(&q->pq[i].queue)); + OSMO_ASSERT(llist_empty(&o->pq[i].queue)); + llist_splice_init(&new_queue, &q->pq[i].queue); + } + + o->queue_size = 0; + o->queue_octets = 0; + q->queue_size = queue_size; + q->queue_octets = queue_octets; +} + +/* Prepend / Put back a previously dequeued LLC frame (llc_queue_dequeue()) */ +void llc_queue_merge_prepend(struct gprs_llc_queue *q, struct gprs_llc *llc) +{ + struct MetaInfo *meta_storage; + unsigned int len = llc_frame_length(llc); + struct msgb *llc_msg = msgb_alloc(len, "llc_pdu_queue"); + + OSMO_ASSERT(llc_msg); + memcpy(msgb_put(llc_msg, len), llc->frame, len); + + q->queue_size += 1; + q->queue_octets += msgb_length(llc_msg); + + meta_storage = (struct MetaInfo *)&llc_msg->cb[0]; + memcpy(meta_storage, &llc->meta_info, sizeof(struct MetaInfo)); + + /* Prepend: */ + llist_add(&llc_msg->list, &q->pq[llc->prio].queue); +} + +#define ALPHA 0.5f + +static struct msgb *llc_queue_pick_msg(struct gprs_llc_queue *q, enum gprs_llc_queue_prio *prio) +{ + struct msgb *msg; + struct timespec tv_now, tv_result; + uint32_t lifetime; + unsigned int i; + const struct MetaInfo *meta_storage; + + for (i = 0; i < ARRAY_SIZE(q->pq); i++) { + if ((msg = msgb_dequeue(&q->pq[i].queue))) { + *prio = (enum gprs_llc_queue_prio)i; + break; + } + } + if (!msg) + return NULL; + + meta_storage = (struct MetaInfo *)&msg->cb[0]; + + q->queue_size -= 1; + q->queue_octets -= msgb_length(msg); + + /* take the second time */ + osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now); + timespecsub(&tv_now, &meta_storage->recv_time, &tv_result); + + lifetime = tv_result.tv_sec*1000 + tv_result.tv_nsec/1000000; + q->avg_queue_delay = q->avg_queue_delay * ALPHA + lifetime * (1-ALPHA); + + return msg; +} + +struct msgb *llc_queue_dequeue(struct gprs_llc_queue *q, enum gprs_llc_queue_prio *out_prio, struct MetaInfo *out_info) +{ + struct msgb *msg; + struct timespec tv_now, tv_now2; + uint32_t octets = 0, frames = 0; + struct gprs_rlcmac_bts *bts = q->ms->bts; + struct gprs_pcu *pcu = bts->pcu; + struct timespec hyst_delta = {0, 0}; + enum gprs_llc_queue_prio prio; + const struct MetaInfo *info = NULL; + + if (pcu->vty.llc_discard_csec) + csecs_to_timespec(pcu->vty.llc_discard_csec, &hyst_delta); + + osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now); + timespecadd(&tv_now, &hyst_delta, &tv_now2); + + while ((msg = llc_queue_pick_msg(q, &prio))) { + info = (const struct MetaInfo *)&msg->cb[0]; + const struct timespec *tv_disc = &info->expire_time; + const struct timespec *tv_recv = &info->recv_time; + + gprs_bssgp_update_queue_delay(tv_recv, &tv_now); + + if (q->use_codel) { + int bytes = llc_queue_octets(q); + if (gprs_codel_control(&q->pq[prio].codel_state, tv_recv, &tv_now, bytes)) + goto drop_frame; + } + + /* Is the age below the low water mark? */ + if (!llc_queue_is_frame_expired(&tv_now2, tv_disc)) + break; + + /* Is the age below the high water mark */ + if (!llc_queue_is_frame_expired(&tv_now, tv_disc)) { + /* Has the previous message not been dropped? */ + if (frames == 0) + break; + + /* Hysteresis mode, try to discard LLC messages until + * the low water mark has been reached */ + + /* Check whether to abort the hysteresis mode: + * Can the PDU be discarded according to its type? */ + if (!llc_pdu_can_be_discarded(msg->data, msg->len)) + break; + } + + bts_do_rate_ctr_inc(bts, CTR_LLC_FRAME_TIMEDOUT); +drop_frame: + frames++; + octets += msg->len; + msgb_free(msg); + bts_do_rate_ctr_inc(bts, CTR_LLC_FRAME_DROPPED); + continue; + } + + if (frames) { + LOGPMS(q->ms, DTBFDL, LOGL_NOTICE, "Discarding LLC PDU " + "because lifetime limit reached, " + "count=%u new_queue_size=%zu\n", + frames, llc_queue_size(q)); + if (frames > 0xff) + frames = 0xff; + if (octets > 0xffffff) + octets = 0xffffff; + if (pcu->bssgp.bctx) + bssgp_tx_llc_discarded(pcu->bssgp.bctx, ms_tlli(q->ms), frames, octets); + } + + if (!msg) + return NULL; + + if (out_prio) + *out_prio = prio; + + if (out_info) { + OSMO_ASSERT(info); + *out_info = *info; + } + + return msg; +} + +void llc_queue_calc_pdu_lifetime(struct gprs_rlcmac_bts *bts, const uint16_t pdu_delay_csec, struct timespec *tv) +{ + uint16_t delay_csec; + if (bts->pcu->vty.force_llc_lifetime) + delay_csec = bts->pcu->vty.force_llc_lifetime; + else + delay_csec = pdu_delay_csec; + + /* keep timestamp at 0 for infinite delay */ + if (delay_csec == 0xffff) { + memset(tv, 0, sizeof(*tv)); + return; + } + + /* calculate timestamp of timeout */ + struct timespec now, csec; + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + csecs_to_timespec(delay_csec, &csec); + + timespecadd(&now, &csec, tv); +} + +bool llc_queue_is_frame_expired(const struct timespec *tv_now, const struct timespec *tv) +{ + /* Timeout is infinite */ + if (tv->tv_sec == 0 && tv->tv_nsec == 0) + return false; + + return timespeccmp(tv_now, tv, >); +} diff --git a/src/llc.cpp b/src/llc.cpp deleted file mode 100644 index e508d0b1..00000000 --- a/src/llc.cpp +++ /dev/null @@ -1,256 +0,0 @@ -/* Copied from tbf.cpp - * - * Copyright (C) 2012 Ivan Klyuchnikov - * Copyright (C) 2012 Andreas Eversberg <jolly@eversberg.eu> - * Copyright (C) 2013 by Holger Hans Peter Freyther - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include <bts.h> - -#include <stdio.h> - -extern "C" { -#include <osmocom/core/msgb.h> -} - -#include "pcu_utils.h" - -/* reset LLC frame */ -void gprs_llc::reset() -{ - m_index = 0; - m_length = 0; - - memset(frame, 0x42, sizeof(frame)); -} - -void gprs_llc::reset_frame_space() -{ - m_index = 0; -} - -/* Put an Unconfirmed Information (UI) Dummy command, see GSM 44.064, 6.4.2.2 */ -void gprs_llc::put_dummy_frame(size_t req_len) -{ - /* The shortest dummy command (the spec requests at least 6 octets) */ - static const uint8_t llc_dummy_command[] = { - 0x43, 0xc0, 0x01, 0x2b, 0x2b, 0x2b - }; - static const size_t max_dummy_command_len = 79; - - put_frame(llc_dummy_command, sizeof(llc_dummy_command)); - - if (req_len > max_dummy_command_len) - req_len = max_dummy_command_len; - - /* Add further stuffing, if the requested length exceeds the minimum - * dummy command length */ - if (m_length < req_len) { - memset(&frame[m_length], 0x2b, req_len - m_length); - m_length = req_len; - } -} - -void gprs_llc::put_frame(const uint8_t *data, size_t len) -{ - /* only put frames when we are empty */ - OSMO_ASSERT(m_index == 0 && m_length == 0); - append_frame(data, len); -} - -void gprs_llc::append_frame(const uint8_t *data, size_t len) -{ - /* TODO: bounds check */ - memcpy(frame + m_length, data, len); - m_length += len; -} - -void gprs_llc::init() -{ - reset(); -} - -bool gprs_llc::is_user_data_frame(uint8_t *data, size_t len) -{ - if (len < 2) - return false; - - if ((data[0] & 0x0f) == 1 /* GPRS_SAPI_GMM */) - return false; - - if ((data[0] & 0xe0) != 0xc0 /* LLC UI */) - /* It is not an LLC UI frame, see TS 44.064, 6.3 */ - return false; - - return true; -} - -void llc_queue_init(struct gprs_llc_queue *q) -{ - INIT_LLIST_HEAD(&q->m_queue); - q->m_queue_size = 0; - q->m_queue_octets = 0; - q->m_avg_queue_delay = 0; -} - - -void gprs_llc_queue::enqueue(struct msgb *llc_msg, const struct timespec *expire_time) -{ - MetaInfo *meta_storage; - - osmo_static_assert(sizeof(*meta_storage) <= sizeof(llc_msg->cb), info_does_not_fit); - - m_queue_size += 1; - m_queue_octets += msgb_length(llc_msg); - - meta_storage = (MetaInfo *)&llc_msg->cb[0]; - osmo_clock_gettime(CLOCK_MONOTONIC, &meta_storage->recv_time); - meta_storage->expire_time = *expire_time; - - msgb_enqueue(&m_queue, llc_msg); -} - -void llc_queue_clear(struct gprs_llc_queue *q, struct gprs_rlcmac_bts *bts) -{ - struct msgb *msg; - - while ((msg = msgb_dequeue(&q->m_queue))) { - if (bts) - bts_do_rate_ctr_inc(bts, CTR_LLC_FRAME_DROPPED); - msgb_free(msg); - } - - q->m_queue_size = 0; - q->m_queue_octets = 0; -} - -void llc_queue_move_and_merge(struct gprs_llc_queue *q, struct gprs_llc_queue *o) -{ - struct msgb *msg, *msg1 = NULL, *msg2 = NULL; - struct llist_head new_queue; - size_t queue_size = 0; - size_t queue_octets = 0; - INIT_LLIST_HEAD(&new_queue); - - while (1) { - if (msg1 == NULL) - msg1 = msgb_dequeue(&q->m_queue); - - if (msg2 == NULL) - msg2 = msgb_dequeue(&o->m_queue); - - if (msg1 == NULL && msg2 == NULL) - break; - - if (msg1 == NULL) { - msg = msg2; - msg2 = NULL; - } else if (msg2 == NULL) { - msg = msg1; - msg1 = NULL; - } else { - const MetaInfo *mi1 = (MetaInfo *)&msg1->cb[0]; - const MetaInfo *mi2 = (MetaInfo *)&msg2->cb[0]; - - if (timespeccmp(&mi2->recv_time, &mi1->recv_time, >)) { - msg = msg1; - msg1 = NULL; - } else { - msg = msg2; - msg2 = NULL; - } - } - - msgb_enqueue(&new_queue, msg); - queue_size += 1; - queue_octets += msgb_length(msg); - } - - OSMO_ASSERT(llist_empty(&q->m_queue)); - OSMO_ASSERT(llist_empty(&o->m_queue)); - - o->m_queue_size = 0; - o->m_queue_octets = 0; - - llist_splice_init(&new_queue, &q->m_queue); - q->m_queue_size = queue_size; - q->m_queue_octets = queue_octets; -} - -#define ALPHA 0.5f - -struct msgb *gprs_llc_queue::dequeue(const MetaInfo **info) -{ - struct msgb *msg; - struct timespec *tv, tv_now, tv_result; - uint32_t lifetime; - const MetaInfo *meta_storage; - - msg = msgb_dequeue(&m_queue); - if (!msg) - return NULL; - - meta_storage = (MetaInfo *)&msg->cb[0]; - - if (info) - *info = meta_storage; - - m_queue_size -= 1; - m_queue_octets -= msgb_length(msg); - - /* take the second time */ - osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now); - tv = (struct timespec *)&msg->data[sizeof(*tv)]; - timespecsub(&tv_now, &meta_storage->recv_time, &tv_result); - - lifetime = tv_result.tv_sec*1000 + tv_result.tv_nsec/1000000; - m_avg_queue_delay = m_avg_queue_delay * ALPHA + lifetime * (1-ALPHA); - - return msg; -} - -void gprs_llc_queue::calc_pdu_lifetime(struct gprs_rlcmac_bts *bts, const uint16_t pdu_delay_csec, struct timespec *tv) -{ - uint16_t delay_csec; - if (bts->pcu->vty.force_llc_lifetime) - delay_csec = bts->pcu->vty.force_llc_lifetime; - else - delay_csec = pdu_delay_csec; - - /* keep timestamp at 0 for infinite delay */ - if (delay_csec == 0xffff) { - memset(tv, 0, sizeof(*tv)); - return; - } - - /* calculate timestamp of timeout */ - struct timespec now, csec; - osmo_clock_gettime(CLOCK_MONOTONIC, &now); - csecs_to_timespec(delay_csec, &csec); - - timespecadd(&now, &csec, tv); -} - -bool gprs_llc_queue::is_frame_expired(const struct timespec *tv_now, - const struct timespec *tv) -{ - /* Timeout is infinite */ - if (tv->tv_sec == 0 && tv->tv_nsec == 0) - return false; - - return timespeccmp(tv_now, tv, >); -} @@ -1,5 +1,6 @@ -/* +/* 3GPP TS 44.064 * Copyright (C) 2013 by Holger Hans Peter Freyther + * Copyright (C) 2022 by by Sysmocom s.f.m.c. GmbH * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -10,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -21,112 +18,138 @@ #ifdef __cplusplus extern "C" { #endif - #include <osmocom/core/linuxlist.h> -#ifdef __cplusplus -} -#endif #include <stdint.h> #include <string.h> #include <time.h> -#define LLC_MAX_LEN 1543 - -struct gprs_rlcmac_bts; +#include <osmocom/core/endian.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/endian.h> -/** - * I represent the LLC data to a MS - */ -struct gprs_llc { +#include "gprs_codel.h" -#ifdef __cplusplus - static bool is_user_data_frame(uint8_t *data, size_t len); - - void init(); - void reset(); - void reset_frame_space(); +#define LLC_MAX_LEN 1543 - void put_frame(const uint8_t *data, size_t len); - void put_dummy_frame(size_t req_len); - void append_frame(const uint8_t *data, size_t len); +struct gprs_rlcmac_bts; +struct GprsMs; + +struct gprs_llc_hdr { +#if OSMO_IS_LITTLE_ENDIAN + union { /* 5.2, 6.2.0 */ + uint8_t address; + uint8_t sapi:4, unused:2, c_r:1, pd:1; +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ + union { + uint8_t address; + uint8_t pd:1, c_r:1, unused:2, sapi:4; #endif - - uint8_t frame[LLC_MAX_LEN]; /* current DL or UL frame */ - uint16_t m_index; /* current write/read position of frame */ - uint16_t m_length; /* len of current DL LLC_frame, 0 == no frame */ -}; + }; + uint8_t control[0]; +} __attribute__ ((packed)); struct MetaInfo { struct timespec recv_time; struct timespec expire_time; }; +enum gprs_llc_queue_prio { /* lowest value has highest prio */ + LLC_QUEUE_PRIO_GMM = 0, /* SAPI 1 */ + LLC_QUEUE_PRIO_TOM_SMS, /* SAPI 2,7,8 */ + LLC_QUEUE_PRIO_OTHER, /* Other SAPIs */ + _LLC_QUEUE_PRIO_SIZE /* used to calculate size of enum */ +}; + /** - * I store the LLC frames that come from the SGSN. + * I represent the LLC data to a MS */ -struct gprs_llc_queue { -#ifdef __cplusplus - static void calc_pdu_lifetime(struct gprs_rlcmac_bts *bts, const uint16_t pdu_delay_csec, - struct timespec *tv); - static bool is_frame_expired(const struct timespec *now, - const struct timespec *tv); - static bool is_user_data_frame(uint8_t *data, size_t len); +struct gprs_llc { + uint8_t frame[LLC_MAX_LEN]; /* current DL or UL frame */ + uint16_t index; /* current write/read position of frame */ + uint16_t length; /* len of current DL LLC_frame, 0 == no frame */ - void enqueue(struct msgb *llc_msg, const struct timespec *expire_time); - struct msgb *dequeue(const MetaInfo **info = 0); -#endif - uint32_t m_avg_queue_delay; /* Average delay of data going through the queue */ - size_t m_queue_size; - size_t m_queue_octets; - struct llist_head m_queue; /* queued LLC DL data */ + /* Saved when dequeue from llc_queue; allows re-enqueing in the queue if Tx fails */ + enum gprs_llc_queue_prio prio; + struct MetaInfo meta_info; }; -#ifdef __cplusplus -extern "C" { -#endif -void llc_queue_init(struct gprs_llc_queue *q); -void llc_queue_clear(struct gprs_llc_queue *q, struct gprs_rlcmac_bts *bts); -void llc_queue_move_and_merge(struct gprs_llc_queue *q, struct gprs_llc_queue *o); +void llc_init(struct gprs_llc *llc); +void llc_reset(struct gprs_llc *llc); +void llc_reset_frame_space(struct gprs_llc *llc); + +void llc_put_frame(struct gprs_llc *llc, const uint8_t *data, size_t len); +void llc_put_dummy_frame(struct gprs_llc *llc, size_t req_len); +void llc_append_frame(struct gprs_llc *llc, const uint8_t *data, size_t len); static inline uint16_t llc_chunk_size(const struct gprs_llc *llc) { - return llc->m_length - llc->m_index; + return llc->length - llc->index; } static inline uint16_t llc_remaining_space(const struct gprs_llc *llc) { - return LLC_MAX_LEN - llc->m_length; + return LLC_MAX_LEN - llc->length; } static inline uint16_t llc_frame_length(const struct gprs_llc *llc) { - return llc->m_length; + return llc->length; } static inline void llc_consume(struct gprs_llc *llc, size_t len) { - llc->m_index += len; + llc->index += len; } static inline void llc_consume_data(struct gprs_llc *llc, uint8_t *data, size_t len) { /* copy and increment index */ - memcpy(data, llc->frame + llc->m_index, len); + memcpy(data, llc->frame + llc->index, len); llc_consume(llc, len); } static inline bool llc_fits_in_current_frame(const struct gprs_llc *llc, uint8_t chunk_size) { - return llc->m_length + chunk_size <= LLC_MAX_LEN; + return llc->length + chunk_size <= LLC_MAX_LEN; } +/** + * I store the LLC frames that come from the SGSN. + */ +struct gprs_llc_prio_queue { + struct gprs_codel codel_state; + struct llist_head queue; /* queued LLC DL data. See enum gprs_llc_queue_prio. */ +}; +struct gprs_llc_queue { + struct GprsMs *ms; /* backpointer */ + uint32_t avg_queue_delay; /* Average delay of data going through the queue */ + size_t queue_size; + size_t queue_octets; + bool use_codel; + struct gprs_llc_prio_queue pq[_LLC_QUEUE_PRIO_SIZE]; /* queued LLC DL data. See enum gprs_llc_queue_prio. */ +}; + +void llc_queue_calc_pdu_lifetime(struct gprs_rlcmac_bts *bts, const uint16_t pdu_delay_csec, + struct timespec *tv); +bool llc_queue_is_frame_expired(const struct timespec *tv_now, const struct timespec *tv); + +void llc_queue_init(struct gprs_llc_queue *q, struct GprsMs *ms); +void llc_queue_clear(struct gprs_llc_queue *q, struct gprs_rlcmac_bts *bts); +void llc_queue_set_codel_interval(struct gprs_llc_queue *q, unsigned int interval); +void llc_queue_move_and_merge(struct gprs_llc_queue *q, struct gprs_llc_queue *o); +void llc_queue_merge_prepend(struct gprs_llc_queue *q, struct gprs_llc *llc); +void llc_queue_enqueue(struct gprs_llc_queue *q, struct msgb *llc_msg, const struct timespec *expire_time); +struct msgb *llc_queue_dequeue(struct gprs_llc_queue *q, enum gprs_llc_queue_prio *out_prio, struct MetaInfo *out_info); + static inline size_t llc_queue_size(const struct gprs_llc_queue *q) { - return q->m_queue_size; + return q->queue_size; } static inline size_t llc_queue_octets(const struct gprs_llc_queue *q) { - return q->m_queue_octets; + return q->queue_octets; } #ifdef __cplusplus diff --git a/src/mslot_class.c b/src/mslot_class.c index ff40f297..caab2b51 100644 --- a/src/mslot_class.c +++ b/src/mslot_class.c @@ -14,14 +14,11 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <mslot_class.h> #include <gprs_debug.h> +#include <gprs_pcu.h> #include <osmocom/core/bits.h> #include <osmocom/core/utils.h> @@ -90,7 +87,7 @@ static const struct gprs_ms_multislot_class gprs_ms_multislot_class[] = { static inline const struct gprs_ms_multislot_class *get_mslot_table(uint8_t ms_cl) { - uint8_t index = ms_cl ? ms_cl : DEFAULT_MSLOT_CLASS; + uint8_t index = ms_cl ? ms_cl : the_pcu->vty.msclass_default; if (ms_cl >= ARRAY_SIZE(gprs_ms_multislot_class)) index = 0; diff --git a/src/mslot_class.h b/src/mslot_class.h index 045fb317..0d48c83d 100644 --- a/src/mslot_class.h +++ b/src/mslot_class.h @@ -14,10 +14,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -26,7 +22,7 @@ #include <stddef.h> #include <stdbool.h> -/* 3GPP TS 05.02 Annex B.1 */ +/* 3GPP TS 45.002 Annex B.1 */ #define MS_NA 255 /* N/A */ #define MS_A 254 /* 1 with hopping, 0 without */ @@ -34,8 +30,6 @@ #define MS_C 252 /* 1 with hopping, 0 without (change Tx to Rx)*/ #define MS_TO 251 /* 31 symbol periods (this can be provided by a TA offset, i.e. a minimum TA value) */ -#define DEFAULT_MSLOT_CLASS 12 - #define NO_FREE_TFI 0xffffffff enum { MASK_TT = 0, MASK_TR = 1 }; diff --git a/src/nacc_fsm.c b/src/nacc_fsm.c index ba7bbb78..24954a4e 100644 --- a/src/nacc_fsm.c +++ b/src/nacc_fsm.c @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <unistd.h> @@ -172,7 +168,7 @@ static struct msgb *create_packet_cell_chg_continue(const struct nacc_fsm_ctx *c struct GprsMs *ms = tbf_ms(tbf); unsigned int rrbp; - rc = tbf_check_polling(tbf, data->fn, data->ts, new_poll_fn, &rrbp); + rc = tbf_check_polling(tbf, data->pdch, data->fn, new_poll_fn, &rrbp); if (rc < 0) { LOGP(DTBF, LOGL_ERROR, "Failed registering poll for Pkt Cell Chg Continue (%d)\n", rc); return NULL; @@ -195,7 +191,7 @@ static struct msgb *create_packet_cell_chg_continue(const struct nacc_fsm_ctx *c uint8_t tfi_is_dl = tbf_direction(tbf) == GPRS_RLCMAC_DL_TBF; uint8_t tfi = tbf_tfi(tbf); uint8_t container_id = 0; - write_packet_cell_change_continue(mac_control_block, 1, rrbp, tfi_is_dl, tfi, true, + write_packet_cell_change_continue(mac_control_block, 1, rrbp, tfi_is_dl, tfi, ctx->neigh_key_present, ctx->neigh_key.tgt_arfcn, ctx->neigh_key.tgt_bsic, container_id); LOGP(DNACC, LOGL_DEBUG, "+++++++++++++++++++++++++ TX : Packet Cell Change Continue +++++++++++++++++++++++++\n"); rc = encode_gsm_rlcmac_downlink(&bv, mac_control_block); @@ -206,9 +202,10 @@ static struct msgb *create_packet_cell_chg_continue(const struct nacc_fsm_ctx *c LOGP(DNACC, LOGL_DEBUG, "------------------------- TX : Packet Cell Change Continue -------------------------\n"); rate_ctr_inc(rate_ctr_group_get_ctr(bts_rate_counters(ms->bts), CTR_PKT_CELL_CHG_CONTINUE)); talloc_free(mac_control_block); - tbf_set_polling(tbf, *new_poll_fn, data->ts, PDCH_ULC_POLL_CELL_CHG_CONTINUE); - LOGPTBFDL(tbf, LOGL_DEBUG, "Scheduled 'Packet Cell Change Continue' polling on PACCH (FN=%d, TS=%d)\n", - *new_poll_fn, data->ts); + tbf_set_polling(tbf, data->pdch, *new_poll_fn, PDCH_ULC_POLL_CELL_CHG_CONTINUE); + LOGPTBF(tbf, LOGL_DEBUG, + "Scheduled 'Packet Cell Change Continue' polling on PACCH (FN=%d, TS=%d)\n", + *new_poll_fn, data->pdch->ts_no); return msg; free_ret: @@ -273,19 +270,94 @@ static int fill_rim_ran_info_req(const struct nacc_fsm_ctx *ctx, struct bssgp_ra return 0; } +/* Generate neighbour key information from a given PacketCellCHangeNotification and a BTS object (lac and cid). The + * ctx variable is used for logging only. */ static int fill_neigh_key_from_bts_pkt_cell_chg_not(struct neigh_cache_entry_key *neigh_key, + bool *neigh_key_present, const struct gprs_rlcmac_bts *bts, - const Packet_Cell_Change_Notification_t *notif) + const Packet_Cell_Change_Notification_t *notif, + const struct nacc_fsm_ctx *ctx) { + const Target_Cell_GSM_Notif_t *notif_gsm; + const Target_Cell_3G_Notif_t *notif_3g; + const Target_Cell_4G_Notif_t *notif_4g; + + memset(neigh_key, 0, sizeof(*neigh_key)); + *neigh_key_present = false; + switch (notif->Target_Cell.UnionType) { - case 0: /* GSM */ + case 0: /* GSM */ + notif_gsm = ¬if->Target_Cell.u.Target_Cell_GSM_Notif; + LOGPFSML(ctx->fi, LOGL_NOTICE, "TargetCell: RAT=GSM, ARFCN=%u, BSIC=%u\n", + notif_gsm->ARFCN, notif_gsm->BSIC); + neigh_key->local_lac = bts->cgi_ps.rai.lac.lac; neigh_key->local_ci = bts->cgi_ps.cell_identity; - neigh_key->tgt_arfcn = notif->Target_Cell.u.Target_Cell_GSM_Notif.ARFCN; - neigh_key->tgt_bsic = notif->Target_Cell.u.Target_Cell_GSM_Notif.BSIC; + neigh_key->tgt_arfcn = notif_gsm->ARFCN; + neigh_key->tgt_bsic = notif_gsm->BSIC; + *neigh_key_present = true; return 0; default: - return -ENOTSUP; + switch (notif->Target_Cell.u.Target_Other_RAT_Notif.UnionType) { + case 0: /* UTRAN */ + notif_3g = ¬if->Target_Cell.u.Target_Other_RAT_Notif.u.Target_Cell_3G_Notif; + if (notif_3g->Exist_FDD_Description) { + LOGPFSML(ctx->fi, LOGL_NOTICE, + "TargetCell: RAT=UTRAN, FDD-ARFCN=%u => no system information provided.\n", + notif_3g->FDD_Target_Cell_Notif.FDD_ARFCN); + } else if (notif_3g->Exist_TDD_Description) { + LOGPFSML(ctx->fi, LOGL_NOTICE, + "TargetCell: RAT=UTRAN, TDD-ARFCN=%u => no system information provided.\n", + notif_3g->TDD_Target_Cell.TDD_ARFCN); + } + return 0; + default: + switch (notif->Target_Cell.u.Target_Other_RAT_Notif.u.Target_Other_RAT_2_Notif.UnionType) { + case 0: /* E-UTRAN (and older RAT) */ + notif_4g = + ¬if->Target_Cell.u.Target_Other_RAT_Notif.u.Target_Other_RAT_2_Notif.u.Target_Cell_4G_Notif; + notif_3g = ¬if_4g->Target_Cell_3G_Notif; + if (notif_4g->Exist_Arfcn) { + LOGPFSML(ctx->fi, LOGL_NOTICE, "TargetCell: RAT=GSM, ARFCN=%u, BSIC=%u\n", + notif_4g->Arfcn, notif_4g->bsic); + neigh_key->local_lac = bts->cgi_ps.rai.lac.lac; + neigh_key->local_ci = bts->cgi_ps.cell_identity; + neigh_key->tgt_arfcn = notif_4g->Arfcn; + neigh_key->tgt_bsic = notif_4g->bsic; + *neigh_key_present = true; + return 0; + } + if (notif_4g->Exist_3G_Target_Cell) { + if (notif_3g->Exist_FDD_Description) { + LOGPFSML(ctx->fi, LOGL_NOTICE, + "TargetCell: RAT=UTRAN, FDD-ARFCN=%u => no system information provided.\n", + notif_3g->FDD_Target_Cell_Notif.FDD_ARFCN); + } else if (notif_3g->Exist_TDD_Description) { + LOGPFSML(ctx->fi, LOGL_NOTICE, + "TargetCell: RAT=UTRAN, TDD-ARFCN=%u => no system information provided.\n", + notif_3g->TDD_Target_Cell.TDD_ARFCN); + } + return 0; + } + if (notif_4g->Exist_Eutran_Target_Cell) { + LOGPFSML(ctx->fi, LOGL_NOTICE, + "TargetCell: RAT=E-UTRAN, EARFCN=%u, CI=%u => no system information provided.\n", + notif_4g->Target_EUTRAN_Cell.EARFCN, + notif_4g->Target_EUTRAN_Cell.Physical_Layer_Cell_Identity); + return 0; + } + + /* TODO: do something meaningful with an Eutran_Ccn_Measurement_Report, in case it is + * provided. */ + + LOGPFSML(ctx->fi, LOGL_NOTICE, "TargetCell: (none, invalid)\n"); + return -EINVAL; + default: + LOGPFSML(ctx->fi, LOGL_NOTICE, + "TargetCell: RAT=CSG-UTRAN|CSG-EUTRAN, (not supported)\n"); + return -ENOTSUP; /* TODO: Add support */ + } + } } } @@ -319,17 +391,28 @@ static void handle_retrans_pkt_cell_chg_notif(struct nacc_fsm_ctx *ctx, const Pa { struct gprs_rlcmac_bts *bts = ctx->ms->bts; struct neigh_cache_entry_key neigh_key; + bool neigh_key_present; + int rc; - if (fill_neigh_key_from_bts_pkt_cell_chg_not(&neigh_key, bts, notif) < 0) { - LOGPFSML(ctx->fi, LOGL_NOTICE, "TargetCell type=0x%x not supported\n", - notif->Target_Cell.UnionType); + rc = fill_neigh_key_from_bts_pkt_cell_chg_not(&neigh_key, &neigh_key_present, bts, notif, ctx); + if (rc < 0) { + /* (see comment below) */ if (ctx->fi->state != NACC_ST_TX_CELL_CHG_CONTINUE) nacc_fsm_state_chg(ctx->fi, NACC_ST_TX_CELL_CHG_CONTINUE); return; + } else if (!neigh_key_present) { + /* In case no neighbour key information is present, (This would be the case for UTRAN or EUTRAN cells) + * then we will not provide any system information. Instead we will send the PacketCellChangeContinue + * message immediately. This also applies in the case of re-transmissions. See also: 3GPP TS 48.018, + * section 8c.6.1. */ + nacc_fsm_state_chg(ctx->fi, NACC_ST_TX_CELL_CHG_CONTINUE); + return; } + /* If tgt cell changed, restart resolving it */ if (!neigh_cache_entry_key_eq(&ctx->neigh_key, &neigh_key)) { ctx->neigh_key = neigh_key; + ctx->neigh_key_present = neigh_key_present; nacc_fsm_state_chg(ctx->fi, NACC_ST_WAIT_RESOLVE_RAC_CI); } /* else: ignore it, it's a dup, carry on what we were doing */ @@ -344,80 +427,32 @@ static void st_initial(struct osmo_fsm_inst *fi, uint32_t event, void *data) struct nacc_fsm_ctx *ctx = (struct nacc_fsm_ctx *)fi->priv; struct gprs_rlcmac_bts *bts = ctx->ms->bts; Packet_Cell_Change_Notification_t *notif; + int rc; switch (event) { case NACC_EV_RX_CELL_CHG_NOTIFICATION: notif = (Packet_Cell_Change_Notification_t *)data; - if (fill_neigh_key_from_bts_pkt_cell_chg_not(&ctx->neigh_key, bts, notif) < 0) { - LOGPFSML(fi, LOGL_NOTICE, "TargetCell type=0x%x not supported\n", - notif->Target_Cell.UnionType); + rc = fill_neigh_key_from_bts_pkt_cell_chg_not(&ctx->neigh_key, &ctx->neigh_key_present, bts, notif, ctx); + if (rc < 0) osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); - } else { + else if (!ctx->neigh_key_present) { + /* In case no neighbour key information is present, (This would be the case for UTRAN or EUTRAN + * cells) then we will not provide any system information. Instead we will send the + * PacketCellChangeContinue message immediately. See also: 3GPP TS 48.018, section 8c.6.1. */ + nacc_fsm_state_chg(fi, NACC_ST_TX_CELL_CHG_CONTINUE); + } else nacc_fsm_state_chg(fi, NACC_ST_WAIT_RESOLVE_RAC_CI); - } break; default: OSMO_ASSERT(0); } } -static int send_neigh_addr_req_ctrl_iface(struct nacc_fsm_ctx *ctx) -{ - struct gprs_rlcmac_bts *bts = ctx->ms->bts; - struct gprs_pcu *pcu = bts->pcu; - struct ctrl_cmd *cmd = NULL; - int rc; - - /* We may have changed to this state previously (eg: we are handling - * another Pkt cell Change Notify with different target). Avoid - * re-creating the socket in that case. */ - if (ctx->neigh_ctrl_conn->write_queue.bfd.fd == -1) { - rc = osmo_sock_init2_ofd(&ctx->neigh_ctrl_conn->write_queue.bfd, - AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, - NULL, 0, pcu->vty.neigh_ctrl_addr, pcu->vty.neigh_ctrl_port, - OSMO_SOCK_F_CONNECT); - if (rc < 0) { - LOGPFSML(ctx->fi, LOGL_ERROR, - "Failed to establish CTRL (neighbor resolution) connection to BSC r=%s:%u\n\n", - pcu->vty.neigh_ctrl_addr, pcu->vty.neigh_ctrl_port); - goto err_term; - } - } - - cmd = ctrl_cmd_create(ctx, CTRL_TYPE_GET); - if (!cmd) { - LOGPFSML(ctx->fi, LOGL_ERROR, "CTRL msg creation failed\n"); - goto err_term; - } - - cmd->id = talloc_asprintf(cmd, "%u", arfcn_bsic_2_ctrl_id(ctx->neigh_key.tgt_arfcn, - ctx->neigh_key.tgt_bsic)); - cmd->variable = talloc_asprintf(cmd, "neighbor_resolve_cgi_ps_from_lac_ci.%d.%d.%d.%d", - ctx->neigh_key.local_lac, ctx->neigh_key.local_ci, - ctx->neigh_key.tgt_arfcn, ctx->neigh_key.tgt_bsic); - rc = ctrl_cmd_send(&ctx->neigh_ctrl_conn->write_queue, cmd); - if (rc) { - LOGPFSML(ctx->fi, LOGL_ERROR, "CTRL msg sent failed: %d\n", rc); - goto err_term; - } - - talloc_free(cmd); - return 0; - -err_term: - talloc_free(cmd); - return -1; -} - static int send_neigh_addr_req(struct nacc_fsm_ctx *ctx) { struct gprs_rlcmac_bts *bts = ctx->ms->bts; - /* If PCU was configured to use the old CTRL interface, use it: */ - if (ctx->neigh_ctrl_conn) - return send_neigh_addr_req_ctrl_iface(ctx); - - /* Otherwise, by default the new PCUIF over IPA Abis multiplex proto should be used: */ + /* Using PCUIF over IPA Abis multiplex proto: */ return pcu_tx_neigh_addr_res_req(bts, &ctx->neigh_key); } @@ -437,7 +472,6 @@ static void st_wait_resolve_rac_ci_on_enter(struct osmo_fsm_inst *fi, uint32_t p } /* CGI-PS not in cache, resolve it using BSC Neighbor Resolution CTRL interface */ - LOGPFSML(fi, LOGL_DEBUG, "No CGI-PS found in cache, resolving " NEIGH_CACHE_ENTRY_KEY_FMT "...\n", NEIGH_CACHE_ENTRY_KEY_ARGS(&ctx->neigh_key)); @@ -611,7 +645,7 @@ static void st_cell_chg_continue(struct osmo_fsm_inst *fi, uint32_t event, void data_ctx = (struct nacc_ev_create_rlcmac_msg_ctx *)data; data_ctx->msg = create_packet_cell_chg_continue(ctx, data_ctx, &ctx->continue_poll_fn); if (data_ctx->msg) { - ctx->continue_poll_ts = data_ctx->ts; + ctx->continue_poll_ts = data_ctx->pdch->ts_no; nacc_fsm_state_chg(fi, NACC_ST_WAIT_CELL_CHG_CONTINUE_ACK); } break; @@ -677,7 +711,8 @@ static struct osmo_fsm_state nacc_fsm_states[] = { .in_event_mask = X(NACC_EV_RX_CELL_CHG_NOTIFICATION), .out_state_mask = - X(NACC_ST_WAIT_RESOLVE_RAC_CI), + X(NACC_ST_WAIT_RESOLVE_RAC_CI) | + X(NACC_ST_TX_CELL_CHG_CONTINUE), .name = "INITIAL", .action = st_initial, }, @@ -803,6 +838,7 @@ void nacc_fsm_ctrl_reply_cb(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd, void if (!(tok = strtok_r(NULL, "-", &saveptr))) goto free_ret; cgi_ps.rai.lac.plmn.mnc = atoi(tok); + cgi_ps.rai.lac.plmn.mnc_3_digits = strlen(tok) > 2; if (!(tok = strtok_r(NULL, "-", &saveptr))) goto free_ret; @@ -835,15 +871,6 @@ static int nacc_fsm_ctx_talloc_destructor(struct nacc_fsm_ctx *ctx) ctx->fi = NULL; } - if (ctx->neigh_ctrl_conn) { - if (ctx->neigh_ctrl_conn->write_queue.bfd.fd != -1) { - osmo_wqueue_clear(&ctx->neigh_ctrl_conn->write_queue); - osmo_fd_unregister(&ctx->neigh_ctrl_conn->write_queue.bfd); - close(ctx->neigh_ctrl_conn->write_queue.bfd.fd); - ctx->neigh_ctrl_conn->write_queue.bfd.fd = -1; - } - } - return 0; } @@ -861,19 +888,6 @@ struct nacc_fsm_ctx *nacc_fsm_alloc(struct GprsMs* ms) if (!ctx->fi) goto free_ret; - /* If CTRL ip present, use the old CTRL interface for neighbor resolution */ - if (ms->bts->pcu->vty.neigh_ctrl_addr) { - ctx->neigh_ctrl = ctrl_handle_alloc(ctx, ctx, NULL); - ctx->neigh_ctrl->reply_cb = nacc_fsm_ctrl_reply_cb; - ctx->neigh_ctrl_conn = osmo_ctrl_conn_alloc(ctx, ctx->neigh_ctrl); - if (!ctx->neigh_ctrl_conn) - goto free_ret; - /* Older versions of osmo_ctrl_conn_alloc didn't properly initialize fd to -1, - * so make sure to do it here otherwise fd may be valid fd 0 and cause trouble */ - ctx->neigh_ctrl_conn->write_queue.bfd.fd = -1; - llist_add(&ctx->neigh_ctrl_conn->list_entry, &ctx->neigh_ctrl->ccon_list); - } - return ctx; free_ret: talloc_free(ctx); @@ -902,3 +916,11 @@ bool nacc_fsm_exp_ctrl_ack(const struct nacc_fsm_ctx *ctx, uint32_t fn, uint8_t ctx->continue_poll_fn == fn && ctx->continue_poll_ts == ts; } + +bool tbf_nacc_rts(const struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch) +{ + if (!tbf_is_control_ts(tbf, pdch)) + return false; + + return tbf_is_tfi_assigned(tbf) && ms_nacc_rts(tbf_ms(tbf)); +} diff --git a/src/nacc_fsm.h b/src/nacc_fsm.h index beea0f75..cab62af5 100644 --- a/src/nacc_fsm.h +++ b/src/nacc_fsm.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -26,6 +22,7 @@ struct GprsMs; struct gprs_rlcmac_tbf; +struct gprs_rlcmac_pdch; enum nacc_fsm_event { NACC_EV_RX_CELL_CHG_NOTIFICATION, /* data: Packet_Cell_Change_Notification_t* */ @@ -49,9 +46,8 @@ enum nacc_fsm_states { struct nacc_fsm_ctx { struct osmo_fsm_inst *fi; struct GprsMs* ms; /* back pointer */ - struct ctrl_handle *neigh_ctrl; - struct ctrl_connection *neigh_ctrl_conn; struct neigh_cache_entry_key neigh_key; /* target cell info from MS */ + bool neigh_key_present; /* target cell info from MS is present */ struct osmo_cell_global_id_ps cgi_ps; /* target cell info resolved from req_{arfcn+bsic} */ struct si_cache_value si_info; /* SI info resolved from SGSN, to be sent to MS */ size_t si_info_bytes_sent; /* How many bytes out of si_info->si_len were already sent to MS */ @@ -63,8 +59,8 @@ struct nacc_fsm_ctx { /* passed as data in NACC_EV_CREATE_RLCMAC_MSG */ struct nacc_ev_create_rlcmac_msg_ctx { struct gprs_rlcmac_tbf *tbf; /* target tbf to create messages for */ + const struct gprs_rlcmac_pdch *pdch; /* TS where the created DL ctrl block is to be sent */ uint32_t fn; /* FN where the created DL ctrl block is to be sent */ - uint8_t ts; /* TS where the created DL ctrl block is to be sent */ struct msgb *msg; /* to be filled by FSM during event processing */ }; @@ -77,3 +73,5 @@ bool nacc_fsm_is_waiting_si_resolution(const struct nacc_fsm_ctx *ctx, const struct osmo_cell_global_id_ps *cgi_ps); bool nacc_fsm_exp_ctrl_ack(const struct nacc_fsm_ctx *ctx, uint32_t fn, uint8_t ts); + +bool tbf_nacc_rts(const struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch); diff --git a/src/neigh_cache.c b/src/neigh_cache.c index 3217f408..129e40b9 100644 --- a/src/neigh_cache.c +++ b/src/neigh_cache.c @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <string.h> @@ -73,7 +69,7 @@ static void neigh_cache_schedule_cleanup(struct neigh_cache *cache) } else { timespecsub(&threshold, &now, &result); } - osmo_timer_schedule(&cache->cleanup_timer, result.tv_sec, result.tv_nsec*1000); + osmo_timer_schedule(&cache->cleanup_timer, result.tv_sec, result.tv_nsec / 1000); } struct neigh_cache *neigh_cache_alloc(void *ctx, unsigned int keep_time_sec) @@ -93,6 +89,26 @@ void neigh_cache_set_keep_time_interval(struct neigh_cache *cache, unsigned int neigh_cache_schedule_cleanup(cache); } +static struct neigh_cache_entry *neigh_cache_lookup_entry(struct neigh_cache *cache, + const struct neigh_cache_entry_key *key) +{ + struct neigh_cache_entry *tmp; + llist_for_each_entry(tmp, &cache->list, list) { + if (neigh_cache_entry_key_eq(&tmp->key, key)) + return tmp; + } + return NULL; +} + +const struct osmo_cell_global_id_ps *neigh_cache_lookup_value(struct neigh_cache *cache, + const struct neigh_cache_entry_key *key) +{ + struct neigh_cache_entry *it = neigh_cache_lookup_entry(cache, key); + if (it) + return &it->value; + return NULL; +} + struct neigh_cache_entry *neigh_cache_add(struct neigh_cache *cache, const struct neigh_cache_entry_key *key, const struct osmo_cell_global_id_ps *value) @@ -123,26 +139,6 @@ struct neigh_cache_entry *neigh_cache_add(struct neigh_cache *cache, return it; } -struct neigh_cache_entry *neigh_cache_lookup_entry(struct neigh_cache *cache, - const struct neigh_cache_entry_key *key) -{ - struct neigh_cache_entry *tmp; - llist_for_each_entry(tmp, &cache->list, list) { - if (neigh_cache_entry_key_eq(&tmp->key, key)) - return tmp; - } - return NULL; -} - -const struct osmo_cell_global_id_ps *neigh_cache_lookup_value(struct neigh_cache *cache, - const struct neigh_cache_entry_key *key) -{ - struct neigh_cache_entry *it = neigh_cache_lookup_entry(cache, key); - if (it) - return &it->value; - return NULL; -} - void neigh_cache_free(struct neigh_cache *cache) { struct neigh_cache_entry *it, *tmp; @@ -207,7 +203,7 @@ static void si_cache_schedule_cleanup(struct si_cache *cache) } else { timespecsub(&threshold, &now, &result); } - osmo_timer_schedule(&cache->cleanup_timer, result.tv_sec, result.tv_nsec*1000); + osmo_timer_schedule(&cache->cleanup_timer, result.tv_sec, result.tv_nsec / 1000); } struct si_cache *si_cache_alloc(void *ctx, unsigned int keep_time_sec) diff --git a/src/neigh_cache.h b/src/neigh_cache.h index 90260cd3..777a705b 100644 --- a/src/neigh_cache.h +++ b/src/neigh_cache.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -69,8 +65,6 @@ void neigh_cache_set_keep_time_interval(struct neigh_cache *cache, unsigned int struct neigh_cache_entry *neigh_cache_add(struct neigh_cache *cache, const struct neigh_cache_entry_key *key, const struct osmo_cell_global_id_ps *value); -struct neigh_cache_entry *neigh_cache_lookup_entry(struct neigh_cache *cache, - const struct neigh_cache_entry_key *key); const struct osmo_cell_global_id_ps *neigh_cache_lookup_value(struct neigh_cache *cache, const struct neigh_cache_entry_key *key); void neigh_cache_free(struct neigh_cache *cache); diff --git a/src/osmo-bts-litecell15/lc15_l1_if.c b/src/osmo-bts-litecell15/lc15_l1_if.c index 13e0fc92..3b8b21b0 100644 --- a/src/osmo-bts-litecell15/lc15_l1_if.c +++ b/src/osmo-bts-litecell15/lc15_l1_if.c @@ -34,6 +34,7 @@ #include <lc15_l1_if.h> #include <gprs_debug.h> #include <pcu_l1_if.h> +#include <pcu_l1_if_phy.h> #include <pdch.h> #include <bts.h> @@ -144,6 +145,11 @@ int l1if_connect_pdch(void *obj, uint8_t ts) return l1if_req_pdch(fl1h, msg); } +int l1if_disconnect_pdch(void *obj, uint8_t ts) +{ + return 0; +} + static int handle_ph_readytosend_ind(struct lc15l1_hdl *fl1h, GsmL1_PhReadyToSendInd_t *rts_ind) { @@ -361,7 +367,7 @@ int l1if_pdch_req(void *obj, uint8_t ts, int is_ptcch, uint32_t fn, return 0; } -void *l1if_open_pdch(uint8_t trx_no, uint32_t hlayer1) +void *l1if_open_trx(uint8_t bts_nr, uint8_t trx_no, uint32_t hlayer1, struct gsmtap_inst *gsmtap) { struct lc15l1_hdl *fl1h; int rc; @@ -390,7 +396,7 @@ void *l1if_open_pdch(uint8_t trx_no, uint32_t hlayer1) return fl1h; } -int l1if_close_pdch(void *obj) +int l1if_close_trx(void *obj) { struct lc15l1_hdl *fl1h = obj; if (fl1h) @@ -398,3 +404,8 @@ int l1if_close_pdch(void *obj) talloc_free(fl1h); return 0; } + +int l1if_init(void) +{ + return 0; +} diff --git a/src/osmo-bts-litecell15/lc15bts.h b/src/osmo-bts-litecell15/lc15bts.h index 4c40db0f..31ca9239 100644 --- a/src/osmo-bts-litecell15/lc15bts.h +++ b/src/osmo-bts-litecell15/lc15bts.h @@ -23,24 +23,24 @@ enum l1prim_type { }; enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id); -const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1]; +extern const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1]; GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id); enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id); -const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1]; +extern const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1]; Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id); -const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1]; -const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1]; +extern const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1]; +extern const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1]; -const struct value_string lc15bts_tracef_names[29]; -const struct value_string lc15bts_tracef_docs[29]; +extern const struct value_string lc15bts_tracef_names[29]; +extern const struct value_string lc15bts_tracef_docs[29]; -const struct value_string lc15bts_tch_pl_names[15]; +extern const struct value_string lc15bts_tch_pl_names[15]; -const struct value_string lc15bts_clksrc_names[10]; +extern const struct value_string lc15bts_clksrc_names[10]; -const struct value_string lc15bts_dir_names[6]; +extern const struct value_string lc15bts_dir_names[6]; enum pdch_cs { PDCH_CS_1, @@ -59,6 +59,6 @@ enum pdch_cs { _NUM_PDCH_CS }; -const uint8_t pdch_msu_size[_NUM_PDCH_CS]; +extern const uint8_t pdch_msu_size[_NUM_PDCH_CS]; #endif /* LC15BTS_H */ diff --git a/src/osmo-bts-oc2g/oc2g_l1_if.c b/src/osmo-bts-oc2g/oc2g_l1_if.c index a41e7afe..2c9da725 100644 --- a/src/osmo-bts-oc2g/oc2g_l1_if.c +++ b/src/osmo-bts-oc2g/oc2g_l1_if.c @@ -35,6 +35,7 @@ #include <gprs_debug.h> #include <pcu_l1_if.h> +#include <pcu_l1_if_phy.h> #include <osmocom/pcu/pcuif_proto.h> #include <bts.h> @@ -145,6 +146,11 @@ int l1if_connect_pdch(void *obj, uint8_t ts) return l1if_req_pdch(fl1h, msg); } +int l1if_disconnect_pdch(void *obj, uint8_t ts) +{ + return 0; +} + static int handle_ph_readytosend_ind(struct oc2gl1_hdl *fl1h, GsmL1_PhReadyToSendInd_t *rts_ind) { @@ -366,7 +372,7 @@ int l1if_pdch_req(void *obj, uint8_t ts, int is_ptcch, uint32_t fn, return 0; } -void *l1if_open_pdch(uint8_t trx_no, uint32_t hlayer1) +void *l1if_open_trx(uint8_t bts_nr, uint8_t trx_no, uint32_t hlayer1, struct gsmtap_inst *gsmtap) { struct oc2gl1_hdl *fl1h; int rc; @@ -395,7 +401,7 @@ void *l1if_open_pdch(uint8_t trx_no, uint32_t hlayer1) return fl1h; } -int l1if_close_pdch(void *obj) +int l1if_close_trx(void *obj) { struct oc2gl1_hdl *fl1h = obj; if (fl1h) @@ -403,3 +409,8 @@ int l1if_close_pdch(void *obj) talloc_free(fl1h); return 0; } + +int l1if_init(void) +{ + return 0; +} diff --git a/src/osmo-bts-oc2g/oc2gbts.h b/src/osmo-bts-oc2g/oc2gbts.h index 24deae74..ea17b2df 100644 --- a/src/osmo-bts-oc2g/oc2gbts.h +++ b/src/osmo-bts-oc2g/oc2gbts.h @@ -23,24 +23,24 @@ enum l1prim_type { }; enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id); -const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1]; +extern const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1]; GsmL1_PrimId_t oc2gbts_get_l1prim_conf(GsmL1_PrimId_t id); enum l1prim_type oc2gbts_get_sysprim_type(Oc2g_PrimId_t id); -const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1]; +extern const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1]; Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id); -const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1]; -const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1]; +extern const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1]; +extern const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1]; -const struct value_string oc2gbts_tracef_names[29]; -const struct value_string oc2gbts_tracef_docs[29]; +extern const struct value_string oc2gbts_tracef_names[29]; +extern const struct value_string oc2gbts_tracef_docs[29]; -const struct value_string oc2gbts_tch_pl_names[15]; +extern const struct value_string oc2gbts_tch_pl_names[15]; -const struct value_string oc2gbts_clksrc_names[10]; +extern const struct value_string oc2gbts_clksrc_names[10]; -const struct value_string oc2gbts_dir_names[6]; +extern const struct value_string oc2gbts_dir_names[6]; enum pdch_cs { PDCH_CS_1, @@ -59,6 +59,6 @@ enum pdch_cs { _NUM_PDCH_CS }; -const uint8_t pdch_msu_size[_NUM_PDCH_CS]; +extern const uint8_t pdch_msu_size[_NUM_PDCH_CS]; #endif /* LC15BTS_H */ diff --git a/src/osmo-bts-sysmo/sysmo_l1_if.c b/src/osmo-bts-sysmo/sysmo_l1_if.c index f8b10cd8..e97b9bdf 100644 --- a/src/osmo-bts-sysmo/sysmo_l1_if.c +++ b/src/osmo-bts-sysmo/sysmo_l1_if.c @@ -15,6 +15,7 @@ #include <sysmo_l1_if.h> #include <gprs_debug.h> #include <pcu_l1_if.h> +#include <pcu_l1_if_phy.h> #include <pdch.h> #include <bts.h> @@ -127,6 +128,11 @@ int l1if_connect_pdch(void *obj, uint8_t ts) return l1if_req_pdch(fl1h, msg); } +int l1if_disconnect_pdch(void *obj, uint8_t ts) +{ + return 0; +} + static int handle_ph_readytosend_ind(struct femtol1_hdl *fl1h, GsmL1_PhReadyToSendInd_t *rts_ind) { @@ -346,7 +352,7 @@ int l1if_pdch_req(void *obj, uint8_t ts, int is_ptcch, uint32_t fn, return 0; } -void *l1if_open_pdch(uint8_t trx_no, uint32_t hlayer1, struct gsmtap_inst *gsmtap) +void *l1if_open_trx(uint8_t bts_nr, uint8_t trx_no, uint32_t hlayer1, struct gsmtap_inst *gsmtap) { struct femtol1_hdl *fl1h; int rc; @@ -372,7 +378,7 @@ void *l1if_open_pdch(uint8_t trx_no, uint32_t hlayer1, struct gsmtap_inst *gsmta return fl1h; } -int l1if_close_pdch(void *obj) +int l1if_close_trx(void *obj) { struct femtol1_hdl *fl1h = obj; if (fl1h) @@ -380,3 +386,8 @@ int l1if_close_pdch(void *obj) talloc_free(fl1h); return 0; } + +int l1if_init(void) +{ + return 0; +} diff --git a/src/pcu_l1_if.cpp b/src/pcu_l1_if.cpp index 8d9defc1..fb44bd83 100644 --- a/src/pcu_l1_if.cpp +++ b/src/pcu_l1_if.cpp @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <stdio.h> @@ -44,8 +40,10 @@ extern "C" { #include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/gsm/gsm48_rest_octets.h> #include <osmocom/gsm/sysinfo.h> +#include <osmocom/gsm/gsm0502.h> #include <nacc_fsm.h> +#include <pcu_l1_if_phy.h> } #include <gprs_rlcmac.h> @@ -57,41 +55,26 @@ extern "C" { #include <pdch.h> #include <tbf_ul.h> #include <tbf_dl.h> -#include <gprs_ms_storage.h> - -// FIXME: move this, when changed from c++ to c. -extern "C" { -void *l1if_open_pdch(uint8_t trx_no, uint32_t hlayer1, - struct gsmtap_inst *gsmtap); -int l1if_connect_pdch(void *obj, uint8_t ts); -int l1if_pdch_req(void *obj, uint8_t ts, int is_ptcch, uint32_t fn, - uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len); -} +#include <gprs_ms.h> extern void *tall_pcu_ctx; -#define PAGING_GROUP_LEN 3 +struct e1_ccu_conn_pars { + struct llist_head entry; -/* returns [0,999] on success, > 999 on error */ -uint16_t imsi2paging_group(const char* imsi) -{ - uint16_t pgroup = 0; - size_t len; - - len = (imsi != NULL) ? strlen(imsi) : 0; - if (len < PAGING_GROUP_LEN) - return 0xFFFF; - imsi += len - PAGING_GROUP_LEN; - - while (*imsi != '\0') { - if (!isdigit(*imsi)) - return 0xFFFF; - pgroup *= 10; - pgroup += *imsi - '0'; - imsi++; - } - return pgroup; -} + /* Related air interface */ + uint8_t bts_nr; + uint8_t trx_nr; + uint8_t ts_nr; + + /* E1 communication parameter */ + struct e1_conn_pars e1_conn_pars; +}; + +/* List storage to collect E1 connection information that we receive through the pcu_sock. The collected data serves as + * a lookup table so that we can lookup the E1 connection information for each PDCH (trx number and timeslot number) + * when it is needed. */ +static LLIST_HEAD(e1_ccu_table); /* * PCU messages @@ -248,7 +231,7 @@ void pcu_l1if_tx_ptcch(struct gprs_rlcmac_bts *bts, uint32_t fn, uint8_t block_nr, uint8_t *data, size_t data_len) { - if (the_pcu->gsmtap_categ_mask & (1 << PCU_GSMTAP_C_DL_PTCCH)) + if (data_len && the_pcu->gsmtap_categ_mask & (1 << PCU_GSMTAP_C_DL_PTCCH)) gsmtap_send(the_pcu->gsmtap, arfcn, ts, GSMTAP_CHANNEL_PTCCH, 0, fn, 0, 0, data, data_len); #ifdef ENABLE_DIRECT_PHY if (bts->trx[trx].fl1h) { @@ -266,43 +249,49 @@ void pcu_l1if_tx_ptcch(struct gprs_rlcmac_bts *bts, pcu_tx_data_req(bts, trx, ts, PCU_IF_SAPI_PTCCH, arfcn, fn, block_nr, data, data_len); } -void pcu_l1if_tx_agch(struct gprs_rlcmac_bts *bts, bitvec * block, int plen) +/* Send a MAC block via the access grant channel. This will (obviously) only work for MAC blocks that contain + * an IMMEDIATE ASSIGNMENT. In case the confirm flag is set, the receiving end is required to send a confirmation + * back when the IMMEDIATE ASSIGNMENT has been sent. */ +void pcu_l1if_tx_agch2(struct gprs_rlcmac_bts *bts, bitvec *block, int plen, bool confirm, uint32_t msg_id) { - uint8_t data[GSM_MACBLOCK_LEN]; /* prefix PLEN */ + struct gsm_pcu_if_agch agch = { 0 }; - /* FIXME: why does OpenBTS has no PLEN and no fill in message? */ - bitvec_pack(block, data + 1); - data[0] = (plen << 2) | 0x01; + agch.confirm = confirm; + agch.msg_id = msg_id; + agch.data[0] = (plen << 2) | 0x01; + bitvec_pack(block, agch.data + 1); if (the_pcu->gsmtap_categ_mask & (1 << PCU_GSMTAP_C_DL_AGCH)) - gsmtap_send(the_pcu->gsmtap, 0, 0, GSMTAP_CHANNEL_AGCH, 0, 0, 0, 0, data, GSM_MACBLOCK_LEN); + gsmtap_send(the_pcu->gsmtap, 0, 0, GSMTAP_CHANNEL_AGCH, 0, 0, 0, 0, agch.data, GSM_MACBLOCK_LEN); - pcu_tx_data_req(bts, 0, 0, PCU_IF_SAPI_AGCH, 0, 0, 0, data, GSM_MACBLOCK_LEN); + pcu_tx_data_req(bts, 0, 0, PCU_IF_SAPI_AGCH_2, 0, 0, 0, (uint8_t*)&agch, sizeof(agch)); } -void pcu_l1if_tx_pch(struct gprs_rlcmac_bts *bts, bitvec * block, int plen, uint16_t pgroup) +/* Send a MAC block via the paging channel. This will (obviously) only work for MAC blocks that contain an + * IMMEDIATE ASSIGNMENT or a PAGING COMMAND message. In case the MAC block contains an IMMEDIATE ASSIGNMENT + * message, the receiving end is required to confirm when the IMMEDIATE ASSIGNMENT has been sent. */ +void pcu_l1if_tx_pch2(struct gprs_rlcmac_bts *bts, struct bitvec *block, int plen, bool confirm, + const char *imsi, uint32_t msg_id) { - uint8_t data[PAGING_GROUP_LEN + GSM_MACBLOCK_LEN]; - int i; - - /* prepend paging group */ - for (i = 0; i < PAGING_GROUP_LEN; i++) { - data[PAGING_GROUP_LEN - 1 - i] = '0' + (char)(pgroup % 10); - pgroup = pgroup / 10; - } - OSMO_ASSERT(pgroup == 0); - - /* block provided by upper layer comes without first byte (plen), - * prepend it manually: + struct gsm_pcu_if_pch pch = { 0 }; + + pch.msg_id = msg_id; + if (imsi) + OSMO_STRLCPY_ARRAY(pch.imsi, imsi); + /* OS#6097: if strlen(pch.imsi) == 0: We assume the MS is in non-DRX + * mode (TS 44.060 5.5.1.5) and hence it is listening on all CCCH blocks + * (TS 45.002 6.5.3, 6.5.6). */ - OSMO_ASSERT(sizeof(data) >= PAGING_GROUP_LEN + 1 + block->data_len); - data[3] = (plen << 2) | 0x01; - bitvec_pack(block, data + PAGING_GROUP_LEN + 1); + + pch.confirm = confirm; + pch.data[0] = (plen << 2) | 0x01; + bitvec_pack(block, pch.data + 1); if (the_pcu->gsmtap_categ_mask & (1 << PCU_GSMTAP_C_DL_PCH)) - gsmtap_send(the_pcu->gsmtap, 0, 0, GSMTAP_CHANNEL_PCH, 0, 0, 0, 0, data + 3, GSM_MACBLOCK_LEN); + gsmtap_send(the_pcu->gsmtap, 0, 0, GSMTAP_CHANNEL_PCH, 0, 0, 0, 0, + pch.data, GSM_MACBLOCK_LEN); - pcu_tx_data_req(bts, 0, 0, PCU_IF_SAPI_PCH, 0, 0, 0, data, PAGING_GROUP_LEN + GSM_MACBLOCK_LEN); + pcu_tx_data_req(bts, 0, 0, PCU_IF_SAPI_PCH_2, 0, 0, 0, (uint8_t*)&pch, sizeof(pch)); } int pcu_tx_neigh_addr_res_req(struct gprs_rlcmac_bts *bts, const struct neigh_cache_entry_key *neigh_key) @@ -342,6 +331,9 @@ int pcu_rx_data_ind_pdtch(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_pdch * { int rc; + /* First of all, update TDMA clock: */ + bts_set_current_frame_number(bts, fn); + if (!pdch->is_enabled()) { LOGPDCH(pdch, DL1IF, LOGL_INFO, "Received DATA.ind (PDTCH) on disabled TS\n"); return -EINVAL; @@ -434,6 +426,13 @@ static int pcu_rx_data_ind_bcch(struct gprs_rlcmac_bts *bts, uint8_t *data, uint si_ro = ((struct gsm48_system_information_type_13*)data)->rest_octets; if (osmo_gsm48_rest_octets_si13_decode(&bts->si13_ro_decoded, si_ro) < 0) LOGP(DPCU, LOGL_ERROR, "Error decoding SI13\n"); + /* Update our cached timers from it: */ + osmo_tdef_set(bts->T_defs_bts, 3168, bts->si13_ro_decoded.cell_opts.t3168, OSMO_TDEF_MS); + osmo_tdef_set(bts->T_defs_bts, 3192, bts->si13_ro_decoded.cell_opts.t3192, OSMO_TDEF_MS); + /* Some sanity checks: */ + if (bts->si13_ro_decoded.cell_opts.t3192 >= + osmo_tdef_get(bts->T_defs_bts, 3193, OSMO_TDEF_MS, -1)) + LOGP(DL1IF, LOGL_ERROR, "Timers incorrectly configured! T3192 >= T3193\n"); break; default: LOGP(DL1IF, LOGL_ERROR, @@ -504,22 +503,19 @@ static int pcu_rx_data_ind(struct gprs_rlcmac_bts *bts, struct gsm_pcu_if_data * return rc; } -static int pcu_rx_data_cnf(struct gprs_rlcmac_bts *bts, struct gsm_pcu_if_data *data_cnf) +static int pcu_rx_data_cnf2(struct gprs_rlcmac_bts *bts, struct gsm_pcu_if_data_cnf *data_cnf) { int rc = 0; - int current_fn = bts_current_frame_number(bts); - LOGP(DL1IF, LOGL_DEBUG, "Data confirm received: sapi=%d fn=%d cur_fn=%d\n", - data_cnf->sapi, data_cnf->fn, current_fn); + LOGP(DL1IF, LOGL_DEBUG, "Data confirm received: sapi=%d\n", data_cnf->sapi); switch (data_cnf->sapi) { - case PCU_IF_SAPI_PCH: - if (data_cnf->data[2] == 0x3f) - bts_rcv_imm_ass_cnf(bts, data_cnf->data, data_cnf->fn); + case PCU_IF_SAPI_PCH_2: + case PCU_IF_SAPI_AGCH_2: + bts_rcv_imm_ass_cnf(bts, NULL, data_cnf->msg_id); break; default: - LOGP(DL1IF, LOGL_ERROR, "Received PCU data confirm with " - "unsupported sapi %d\n", data_cnf->sapi); + LOGP(DL1IF, LOGL_ERROR, "Received PCU data confirm with unsupported sapi %d\n", data_cnf->sapi); rc = -EINVAL; } @@ -544,7 +540,7 @@ int pcu_rx_rts_req_ptcch(struct gprs_rlcmac_bts *bts, uint8_t trx, uint8_t ts, /* Make sure PDCH time-slot is enabled */ pdch = &bts->trx[trx].pdch[ts]; - if (!pdch->m_is_enabled) + if (!pdch_is_enabled(pdch)) return -EAGAIN; /* If there's no TBF attached to this PDCH, we can skip Tx of PTCCH @@ -555,11 +551,10 @@ int pcu_rx_rts_req_ptcch(struct gprs_rlcmac_bts *bts, uint8_t trx, uint8_t ts, const unsigned num_tbfs = pdch->num_tbfs(GPRS_RLCMAC_DL_TBF) + pdch->num_tbfs(GPRS_RLCMAC_UL_TBF); bool skip_idle = (num_tbfs == 0); -#ifdef ENABLE_DIRECT_PHY - /* In DIRECT_PHY mode we want to always submit something to L1 in - * TRX0, since BTS is not preparing dummy bursts on idle TS for us: */ + + if (bts->gen_idle_blocks_C0) skip_idle = skip_idle && trx != 0; -#endif + if (skip_idle) { pcu_l1if_tx_ptcch(bts, trx, ts, bts->trx[trx].arfcn, fn, block_nr, NULL, 0); @@ -601,7 +596,7 @@ static int pcu_rx_rts_req(struct gprs_rlcmac_bts *bts, struct gsm_pcu_if_rts_req return rc; } -/* C -> C++ adapter for direct DSP access code (e.g. osmo-bts-sysmo) */ +/* C -> C++ adapter for direct PHY access code (e.g. osmo-bts-sysmo) */ extern "C" int pcu_rx_rach_ind_ptcch(struct gprs_rlcmac_bts *bts, uint8_t trx_nr, uint8_t ts_nr, uint32_t fn, int16_t qta) { struct rach_ind_params rip = { @@ -611,7 +606,8 @@ extern "C" int pcu_rx_rach_ind_ptcch(struct gprs_rlcmac_bts *bts, uint8_t trx_nr .ra = 0x00, .trx_nr = trx_nr, .ts_nr = ts_nr, - .rfn = fn, + .rfn = fn2rfn(fn), + .fn = fn, .qta = qta, }; @@ -621,11 +617,28 @@ extern "C" int pcu_rx_rach_ind_ptcch(struct gprs_rlcmac_bts *bts, uint8_t trx_nr static int pcu_rx_rach_ind(struct gprs_rlcmac_bts *bts, const struct gsm_pcu_if_rach_ind *rach_ind) { int rc = 0; - int current_fn = bts_current_frame_number(bts); + uint32_t current_fn = bts_current_frame_number(bts); + uint16_t rfn; + + /* Note: If a BSC is sending a RACH req to us, it is actually forwarding it to + * us from BTS as a result of receiving an RFN (Fn % 42432) over RSL + * (see 3GPP TS 48.058, section 9.3.19). + * If a BTS is sending a RACH req to us, it may contain a full FN + * (current osmo-bts does that) instead of an RFN. + * For consistency, and taking into account the BSC case limitations, + * work always with RFNs here: + */ + rfn = fn2rfn(rach_ind->fn); + + LOGP(DL1IF, LOGL_INFO, + "RACH request received: sapi=%d qta=%d, ra=0x%02x, fn=%u (rfn=%u), cur_fn=%d, is_11bit=%d\n", + rach_ind->sapi, rach_ind->qta, rach_ind->ra, rach_ind->fn, rfn, current_fn, rach_ind->is_11bit); - LOGP(DL1IF, LOGL_INFO, "RACH request received: sapi=%d " - "qta=%d, ra=0x%02x, fn=%u, cur_fn=%d, is_11bit=%d\n", rach_ind->sapi, rach_ind->qta, - rach_ind->ra, rach_ind->fn, current_fn, rach_ind->is_11bit); + if (OSMO_UNLIKELY(rach_ind->fn > GSM_TDMA_HYPERFRAME - 1)) { + LOGP(DL1IF, LOGL_ERROR, "RACH request contains fn=%u that exceeds valid limits (0-%u) -- ignored!\n", + rach_ind->fn, GSM_TDMA_HYPERFRAME - 1); + return -EINVAL; + } struct rach_ind_params rip = { .burst_type = (enum ph_burst_type) rach_ind->burst_type, @@ -633,7 +646,8 @@ static int pcu_rx_rach_ind(struct gprs_rlcmac_bts *bts, const struct gsm_pcu_if_ .ra = rach_ind->ra, .trx_nr = rach_ind->trx_nr, .ts_nr = rach_ind->ts_nr, - .rfn = rach_ind->fn, + .rfn = rfn, + .fn = bts_rfn_to_fn(bts, rfn), .qta = rach_ind->qta, }; @@ -709,6 +723,38 @@ static int pcu_info_ind_ns(struct gprs_rlcmac_bts *bts, return gprs_ns_update_config(bts, info_ind->nsei, local, remote, nsvci, valid); } +const struct value_string gsm_pcuif_bts_model_names[] = { + { PCU_IF_BTS_MODEL_UNSPEC, "(unspecified)" }, + { PCU_IF_BTS_MODEL_LC15, "osmo-bts-lc15" }, + { PCU_IF_BTS_MODEL_OC2G, "osmo-bts-oc2g" }, + { PCU_IF_BTS_MODEL_OCTPHY, "osmo-bts-octphy" }, + { PCU_IF_BTS_MODEL_SYSMO, "osmo-bts-sysmo" }, + { PCU_IF_BTS_MODEL_TRX, "osmo-bts-trx" }, + { PCU_IF_BTS_MODEL_RBS, "ericsson-rbs" }, + { 0, NULL } +}; + +static bool decide_gen_idle_blocks(struct gprs_rlcmac_bts *bts) +{ + switch (bts->bts_model) { + case PCU_IF_BTS_MODEL_UNSPEC: + case PCU_IF_BTS_MODEL_LC15: + case PCU_IF_BTS_MODEL_OC2G: + case PCU_IF_BTS_MODEL_OCTPHY: + case PCU_IF_BTS_MODEL_SYSMO: + case PCU_IF_BTS_MODEL_RBS: + /* The BTS models above do not generate dummy blocks by themselves, so OsmoPCU must fill the idle gaps in the + * stream of generated PDCH blocks with dummy blocks. */ + return true; + case PCU_IF_BTS_MODEL_TRX: + /* The BTS models above generate dummy blocks by themselves, so OsmoBTS will only generate PDCH bloks that + * actually contain data. On idle, no blocks are generated. */ + return false; + default: + return false; + } +} + static int pcu_rx_info_ind(struct gprs_rlcmac_bts *bts, const struct gsm_pcu_if_info_ind *info_ind) { struct gprs_bssgp_pcu *pcu; @@ -716,14 +762,18 @@ static int pcu_rx_info_ind(struct gprs_rlcmac_bts *bts, const struct gsm_pcu_if_ unsigned int trx_nr, ts_nr; unsigned int i; + if (llist_count(&the_pcu->bts_list) > 1) + LOGP(DL1IF, LOGL_ERROR, "more than one BTS regsitered at this PCU. This PCU has only been tested with one BTS! OS#5930\n"); + + LOGP(DL1IF, LOGL_DEBUG, "Info indication received:\n"); + if (info_ind->version != PCU_IF_VERSION) { - fprintf(stderr, "PCU interface version number of BTS (%u) is " - "different (%u).\nPlease re-compile!\n", + fprintf(stderr, "PCU interface version number of BTS/BSC (%u) is different (%u).\nPlease use a BTS/BSC with a compatble interface!\n", info_ind->version, PCU_IF_VERSION); exit(-1); } - LOGP(DL1IF, LOGL_DEBUG, "Info indication received:\n"); + the_pcu->pcu_if_version = info_ind->version; if (!(info_ind->flags & PCU_IF_FLAG_ACTIVE)) { LOGP(DL1IF, LOGL_NOTICE, "BTS not available\n"); @@ -735,7 +785,8 @@ bssgp_failed: for (trx_nr = 0; trx_nr < ARRAY_SIZE(bts->trx); trx_nr++) { bts->trx[trx_nr].arfcn = info_ind->trx[trx_nr].arfcn; for (ts_nr = 0; ts_nr < ARRAY_SIZE(bts->trx[0].pdch); ts_nr++) - bts->trx[trx_nr].pdch[ts_nr].disable(); + if (bts->trx[trx_nr].pdch[ts_nr].is_enabled()) + bts->trx[trx_nr].pdch[ts_nr].disable(); } gprs_bssgp_destroy(bts); exit(0); @@ -841,23 +892,23 @@ bssgp_failed: for (trx_nr = 0; trx_nr < ARRAY_SIZE(bts->trx); trx_nr++) { bts->trx[trx_nr].arfcn = info_ind->trx[trx_nr].arfcn; - if ((info_ind->flags & PCU_IF_FLAG_SYSMO) + if ((info_ind->flags & PCU_IF_FLAG_DIRECT_PHY) && info_ind->trx[trx_nr].hlayer1) { #ifdef ENABLE_DIRECT_PHY LOGP(DL1IF, LOGL_DEBUG, " TRX %d hlayer1=%x\n", trx_nr, info_ind->trx[trx_nr].hlayer1); if (!bts->trx[trx_nr].fl1h) - bts->trx[trx_nr].fl1h = l1if_open_pdch( - trx_nr, + bts->trx[trx_nr].fl1h = l1if_open_trx( + bts->nr, trx_nr, info_ind->trx[trx_nr].hlayer1, the_pcu->gsmtap); if (!bts->trx[trx_nr].fl1h) { LOGP(DL1IF, LOGL_FATAL, "Failed to open direct " - "DSP access for PDCH.\n"); + "PHY access for PDCH.\n"); exit(0); } #else - LOGP(DL1IF, LOGL_FATAL, "Compiled without direct DSP " + LOGP(DL1IF, LOGL_FATAL, "Compiled without direct PHY " "access for PDCH, but enabled at " "BTS. Please deactivate it!\n"); exit(0); @@ -865,14 +916,14 @@ bssgp_failed: } for (ts_nr = 0; ts_nr < ARRAY_SIZE(bts->trx[0].pdch); ts_nr++) { - const struct gsm_pcu_if_info_ts *its = &info_ind->trx[trx_nr].ts[ts_nr]; + const struct gsm_pcu_if_info_trx_ts *its = &info_ind->trx[trx_nr].ts[ts_nr]; struct gprs_rlcmac_pdch *pdch = &bts->trx[trx_nr].pdch[ts_nr]; if ((info_ind->trx[trx_nr].pdch_mask & (1 << ts_nr))) { /* FIXME: activate dynamically at RLCMAC */ if (!pdch->is_enabled()) { #ifdef ENABLE_DIRECT_PHY if ((info_ind->flags & - PCU_IF_FLAG_SYSMO)) + PCU_IF_FLAG_DIRECT_PHY)) l1if_connect_pdch( bts->trx[trx_nr].fl1h, ts_nr); #endif @@ -883,7 +934,7 @@ bssgp_failed: pdch->tsc = its->tsc; /* (Optional) frequency hopping parameters */ - if (its->h) { + if (its->hopping) { pdch->fh.enabled = true; pdch->fh.maio = its->maio; pdch->fh.hsn = its->hsn; @@ -902,6 +953,10 @@ bssgp_failed: trx_nr, ts_nr, pdch->tsc, pdch->fh.enabled ? "yes" : "no"); } else { if (pdch->is_enabled()) { +#ifdef ENABLE_DIRECT_PHY + if ((info_ind->flags & PCU_IF_FLAG_DIRECT_PHY)) + l1if_disconnect_pdch(bts->trx[trx_nr].fl1h, ts_nr); +#endif pcu_tx_act_req(bts, pdch, 0); pdch->disable(); } @@ -909,16 +964,85 @@ bssgp_failed: } } + LOGP(DL1IF, LOGL_INFO, "BTS model: %s\n", get_value_string(gsm_pcuif_bts_model_names, info_ind->bts_model)); + bts->bts_model = info_ind->bts_model; + bts->gen_idle_blocks_C0 = decide_gen_idle_blocks(bts); + bts->active = true; return rc; } -static int pcu_rx_time_ind(struct gprs_rlcmac_bts *bts, struct gsm_pcu_if_time_ind *time_ind) +/* Query E1 CCU connection parameters by TS and TRX number */ +int pcu_l1if_get_e1_ccu_conn_pars(struct e1_conn_pars **e1_conn_pars, uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr) +{ + struct e1_ccu_conn_pars *e1_ccu_conn_pars; + + llist_for_each_entry(e1_ccu_conn_pars, &e1_ccu_table, entry) { + if (e1_ccu_conn_pars->bts_nr == bts_nr && e1_ccu_conn_pars->trx_nr == trx_nr + && e1_ccu_conn_pars->ts_nr == ts_nr) { + *e1_conn_pars = &e1_ccu_conn_pars->e1_conn_pars; + return 0; + } + } + + return -EINVAL; +} + +/* Allocate a new connection parameter struct and store connection parameters */ +static void new_e1_ccu_conn_pars(const struct gsm_pcu_if_e1_ccu_ind *e1_ccu_ind, uint8_t bts_nr) +{ + struct e1_ccu_conn_pars *e1_ccu_conn_pars; + + e1_ccu_conn_pars = talloc_zero(tall_pcu_ctx, struct e1_ccu_conn_pars); + OSMO_ASSERT(e1_ccu_conn_pars); + e1_ccu_conn_pars->bts_nr = bts_nr; + e1_ccu_conn_pars->trx_nr = e1_ccu_ind->trx_nr; + e1_ccu_conn_pars->ts_nr = e1_ccu_ind->ts_nr; + e1_ccu_conn_pars->e1_conn_pars.e1_nr = e1_ccu_ind->e1_nr; + e1_ccu_conn_pars->e1_conn_pars.e1_ts = e1_ccu_ind->e1_ts; + e1_ccu_conn_pars->e1_conn_pars.e1_ts_ss = e1_ccu_ind->e1_ts_ss; + llist_add(&e1_ccu_conn_pars->entry, &e1_ccu_table); +} + +static int pcu_rx_e1_ccu_ind(struct gprs_rlcmac_bts *bts, const struct gsm_pcu_if_e1_ccu_ind *e1_ccu_ind) { - uint8_t fn13 = time_ind->fn % 13; + struct e1_conn_pars *e1_conn_pars; + uint8_t rate; + uint8_t subslot_nr; + int rc; + + /* only used with log statement below, no technical relevance otherwise. */ + if (e1_ccu_ind->e1_ts_ss > 3) { + rate = 64; + subslot_nr = 0; + } else { + rate = 16; + subslot_nr = e1_ccu_ind->e1_ts_ss; + } + + LOGP(DL1IF, LOGL_NOTICE, + "(ts=%u,trx=%u) new E1 CCU communication parameters for CCU (E1-line:%u, E1-TS:%u, E1-SS:%u, rate:%ukbps)\n", + e1_ccu_ind->ts_nr, e1_ccu_ind->trx_nr, e1_ccu_ind->e1_nr, e1_ccu_ind->e1_ts, + subslot_nr, rate); + + /* Search for an existing entry, when found, update it. */ + rc = pcu_l1if_get_e1_ccu_conn_pars(&e1_conn_pars, bts->nr, e1_ccu_ind->trx_nr, e1_ccu_ind->ts_nr); + if (rc == 0) { + e1_conn_pars->e1_nr = e1_ccu_ind->e1_nr; + e1_conn_pars->e1_ts = e1_ccu_ind->e1_ts; + e1_conn_pars->e1_ts_ss = e1_ccu_ind->e1_ts_ss; + return 0; + } + + /* Create new connection parameter entry */ + new_e1_ccu_conn_pars(e1_ccu_ind, bts->nr); + return 0; +} +static int pcu_rx_time_ind(struct gprs_rlcmac_bts *bts, struct gsm_pcu_if_time_ind *time_ind) +{ /* omit frame numbers not starting at a MAC block */ - if (fn13 != 0 && fn13 != 4 && fn13 != 8) + if (!fn_valid(time_ind->fn)) return 0; LOGP(DL1IF, LOGL_DEBUG, "Time indication received: %d\n", time_ind->fn % 52); @@ -961,7 +1085,7 @@ static int pcu_rx_pag_req(struct gprs_rlcmac_bts *bts, struct gsm_pcu_if_pag_req case GSM_MI_TYPE_IMSI: req.mi_imsi = mi; req.mi_imsi_present = true; - ms = bts_ms_by_imsi(bts, req.mi_imsi.imsi); + ms = bts_get_ms_by_imsi(bts, req.mi_imsi.imsi); break; default: LOGP(DL1IF, LOGL_ERROR, "Unexpected MI type %u\n", mi.type); @@ -984,7 +1108,7 @@ static int pcu_rx_susp_req(struct gprs_rlcmac_bts *bts, struct gsm_pcu_if_susp_r LOGP(DL1IF, LOGL_INFO, "GPRS Suspend request received: TLLI=0x%08x RAI=%s\n", susp_req->tlli, osmo_rai_name(&ra_id)); - if ((ms = bts_ms_store(bts)->get_ms(susp_req->tlli))) { + if ((ms = bts_get_ms_by_tlli(bts, susp_req->tlli, GSM_RESERVED_TMSI))) { /* We need to catch both pointers here since MS may become freed after first tbf_free(dl_tbf) if only DL TBF was available */ dl_tbf = ms_dl_tbf(ms); @@ -1009,8 +1133,8 @@ static int pcu_rx_app_info_req(struct gprs_rlcmac_bts *bts, struct gsm_pcu_if_ap app_info_req->application_type, app_info_req->len); bts->app_info_pending = 0; - llist_for_each(tmp, bts_ms_store(bts)->ms_list()) { - GprsMs *ms = llist_entry(tmp, typeof(*ms), list); + llist_for_each(tmp, &bts->ms_list) { + struct GprsMs *ms = llist_entry(tmp, typeof(*ms), list); if (!ms_dl_tbf(ms)) continue; bts->app_info_pending++; @@ -1067,8 +1191,8 @@ static int pcu_rx_neigh_addr_cnf(struct gprs_rlcmac_bts *bts, struct gsm_pcu_if_ NEIGH_CACHE_ENTRY_KEY_ARGS(&neigh_key), naddr_cnf->err_code); } - llist_for_each(tmp, bts_ms_store(bts)->ms_list()) { - GprsMs *ms = llist_entry(tmp, typeof(*ms), list); + llist_for_each(tmp, &bts->ms_list) { + struct GprsMs *ms = llist_entry(tmp, typeof(*ms), list); if (ms->nacc && nacc_fsm_is_waiting_addr_resolution(ms->nacc, &neigh_key)) osmo_fsm_inst_dispatch(ms->nacc->fi, NACC_EV_RX_RAC_CI, cgi_ps_ptr); } @@ -1125,9 +1249,9 @@ int pcu_rx(struct gsm_pcu_if *pcu_prim, size_t pcu_prim_length) CHECK_IF_MSG_SIZE(pcu_prim_length, pcu_prim->u.data_ind); rc = pcu_rx_data_ind(bts, &pcu_prim->u.data_ind); break; - case PCU_IF_MSG_DATA_CNF: - CHECK_IF_MSG_SIZE(pcu_prim_length, pcu_prim->u.data_cnf); - rc = pcu_rx_data_cnf(bts, &pcu_prim->u.data_cnf); + case PCU_IF_MSG_DATA_CNF_2: + CHECK_IF_MSG_SIZE(pcu_prim_length, pcu_prim->u.data_cnf2); + rc = pcu_rx_data_cnf2(bts, &pcu_prim->u.data_cnf2); break; case PCU_IF_MSG_RTS_REQ: CHECK_IF_MSG_SIZE(pcu_prim_length, pcu_prim->u.rts_req); @@ -1141,6 +1265,10 @@ int pcu_rx(struct gsm_pcu_if *pcu_prim, size_t pcu_prim_length) CHECK_IF_MSG_SIZE(pcu_prim_length, pcu_prim->u.info_ind); rc = pcu_rx_info_ind(bts, &pcu_prim->u.info_ind); break; + case PCU_IF_MSG_E1_CCU_IND: + CHECK_IF_MSG_SIZE(pcu_prim_length, pcu_prim->u.e1_ccu_ind); + rc = pcu_rx_e1_ccu_ind(bts, &pcu_prim->u.e1_ccu_ind); + break; case PCU_IF_MSG_TIME_IND: CHECK_IF_MSG_SIZE(pcu_prim_length, pcu_prim->u.time_ind); rc = pcu_rx_time_ind(bts, &pcu_prim->u.time_ind); @@ -1167,6 +1295,8 @@ int pcu_rx(struct gsm_pcu_if *pcu_prim, size_t pcu_prim_length) if (pcu_prim_length < exp_len) { LOGP(DL1IF, LOGL_ERROR, "Received %zu bytes on PCU Socket, but primitive container size" \ "is %zu, discarding\n", pcu_prim_length, exp_len); + rc = -EINVAL; + break; } rc = pcu_rx_container(bts, &pcu_prim->u.container); break; diff --git a/src/pcu_l1_if.h b/src/pcu_l1_if.h index 7d48febe..b6da337b 100644 --- a/src/pcu_l1_if.h +++ b/src/pcu_l1_if.h @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef PCU_L1_IF_H @@ -150,9 +146,8 @@ void pcu_l1if_tx_ptcch(struct gprs_rlcmac_bts *bts, uint8_t trx, uint8_t ts, uint16_t arfcn, uint32_t fn, uint8_t block_nr, uint8_t *data, size_t data_len); -void pcu_l1if_tx_agch(struct gprs_rlcmac_bts *bts, bitvec * block, int len); - -void pcu_l1if_tx_pch(struct gprs_rlcmac_bts *bts, bitvec * block, int plen, uint16_t pgroup); +void pcu_l1if_tx_agch(struct gprs_rlcmac_bts *bts, bitvec *block, int len); +void pcu_l1if_tx_agch2(struct gprs_rlcmac_bts *bts, bitvec *block, int plen, bool confirm, uint32_t msg_id); #endif #ifdef __cplusplus @@ -161,7 +156,9 @@ extern "C" { struct gprs_rlcmac_bts; int pcu_tx_neigh_addr_res_req(struct gprs_rlcmac_bts *bts, const struct neigh_cache_entry_key *neigh_key); - +void pcu_l1if_tx_pch(struct gprs_rlcmac_bts *bts, struct bitvec *block, int plen, const char *imsi); +void pcu_l1if_tx_pch2(struct gprs_rlcmac_bts *bts, struct bitvec *block, int plen, bool confirm, + const char *imsi, uint32_t msg_id); int pcu_rx(struct gsm_pcu_if *pcu_prim, size_t pcu_prim_length); int pcu_l1if_open(void); void pcu_l1if_close(void); @@ -179,7 +176,17 @@ int pcu_rx_data_ind_pdtch(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_pdch * uint8_t len, uint32_t fn, struct pcu_l1_meas *meas); void pcu_rx_block_time(struct gprs_rlcmac_bts *bts, uint16_t arfcn, uint32_t fn, uint8_t ts_no); -uint16_t imsi2paging_group(const char* imsi); + +struct e1_conn_pars { + /* Number of E1 line */ + uint8_t e1_nr; + /* Number of E1 timeslot */ + uint8_t e1_ts; + /* Number of I.460 subslot inside E1 timeslot */ + uint8_t e1_ts_ss; +}; + +int pcu_l1if_get_e1_ccu_conn_pars(struct e1_conn_pars **e1_conn_pars, uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr); #define PCUIF_HDR_SIZE ( sizeof(struct gsm_pcu_if) - sizeof(((struct gsm_pcu_if *)0)->u) ) diff --git a/src/pcu_l1_if_phy.h b/src/pcu_l1_if_phy.h new file mode 100644 index 00000000..08b7d238 --- /dev/null +++ b/src/pcu_l1_if_phy.h @@ -0,0 +1,12 @@ +#pragma once + +#include <stdint.h> +#include <osmocom/core/gsmtap_util.h> + +int l1if_init(void); +void *l1if_open_trx(uint8_t bts_nr, uint8_t trx_no, uint32_t hlayer1, struct gsmtap_inst *gsmtap); +int l1if_connect_pdch(void *obj, uint8_t ts); +int l1if_pdch_req(void *obj, uint8_t ts, int is_ptcch, uint32_t fn, uint16_t arfcn, uint8_t block_nr, uint8_t *data, + uint8_t len); +int l1if_disconnect_pdch(void *obj, uint8_t ts); +int l1if_close_trx(void *obj); diff --git a/src/pcu_main.cpp b/src/pcu_main.cpp index 003cabb4..ec7bddd4 100644 --- a/src/pcu_main.cpp +++ b/src/pcu_main.cpp @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <pcu_l1_if.h> @@ -32,10 +28,12 @@ #include <bts.h> #include <osmocom/pcu/pcuif_proto.h> #include "gprs_bssgp_pcu.h" +#include "alloc_algo.h" extern "C" { #include "pcu_vty.h" #include "coding_scheme.h" +#include "pcu_l1_if_phy.h" #include <osmocom/gprs/gprs_bssgp.h> #include <osmocom/gprs/gprs_ns2.h> #include <osmocom/vty/telnet_interface.h> @@ -64,7 +62,7 @@ extern void *bv_tall_ctx; static int quit = 0; static int rt_prio = -1; static bool daemonize = false; -static const char *gsmtap_addr = "localhost"; // FIXME: use gengetopt's default value instead +static const char *gsmtap_addr; static void print_help() { @@ -74,9 +72,7 @@ static void print_help() " -m --mcc MCC Use given MCC instead of value provided by BTS\n" " -n --mnc MNC Use given MNC instead of value provided by BTS\n" " -V --version Print version\n" - " -r --realtime PRIO Use SCHED_RR with the specified priority\n" " -D --daemonize Fork the process into a background daemon\n" - " -i --gsmtap-ip The destination IP used for GSMTAP\n" "\nVTY reference generation:\n" " --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n" " --vty-ref-xml Generate the VTY reference XML output and exit.\n" @@ -162,9 +158,13 @@ static void handle_options(int argc, char **argv) break; case 'i': gsmtap_addr = optarg; + fprintf(stderr, "Command line argument '-i' is deprecated, use VTY " + "parameter 'gsmtap-remote-host %s' instead.\n", gsmtap_addr); break; case 'r': rt_prio = atoi(optarg); + fprintf(stderr, "Command line argument '-r' is deprecated, use VTY " + "cpu-sched node setting 'policy rr %d' instead.\n", rt_prio); break; case 'D': daemonize = true; @@ -239,6 +239,7 @@ int main(int argc, char *argv[]) pcu->pcu_sock_path = talloc_strdup(tall_pcu_ctx, PCU_SOCK_DEFAULT); + osmo_fsm_log_addr(false); msgb_talloc_ctx_init(tall_pcu_ctx, 0); osmo_stats_init(tall_pcu_ctx); @@ -250,6 +251,12 @@ int main(int argc, char *argv[]) osmo_cpu_sched_vty_init(tall_pcu_ctx); logging_vty_add_deprecated_subsys(tall_pcu_ctx, "bssgp"); +#ifdef ENABLE_DIRECT_PHY + rc = l1if_init(); + if (rc < 0) + return -EINVAL; +#endif + handle_options(argc, argv); if ((!!spoof_mcc) + (!!spoof_mnc) == 1) { fprintf(stderr, "--mcc and --mnc must be specified " @@ -257,13 +264,6 @@ int main(int argc, char *argv[]) exit(0); } - pcu->gsmtap = gsmtap_source_init(gsmtap_addr, GSMTAP_UDP_PORT, 1); - - if (pcu->gsmtap) - gsmtap_source_add_sink(pcu->gsmtap); - else - fprintf(stderr, "Failed to initialize GSMTAP for %s\n", gsmtap_addr); - pcu->nsi = gprs_ns2_instantiate(tall_pcu_ctx, gprs_ns_prim_cb, NULL); if (!pcu->nsi) { LOGP(DBSSGP, LOGL_ERROR, "Failed to create NS instance\n"); @@ -282,8 +282,32 @@ int main(int argc, char *argv[]) fprintf(stderr, "No config file: '%s' Using default config.\n", config_file); - rc = telnet_init_dynif(tall_pcu_ctx, NULL, vty_get_bind_addr(), - OSMO_VTY_PORT_PCU); + /* Accept a GSMTAP host from VTY config, but a commandline option overrides that. */ + if (gsmtap_addr) { + if (pcu->gsmtap_remote_host != NULL) { + LOGP(DLGLOBAL, LOGL_NOTICE, + "Command line argument '-i %s' overrides " + "'gsmtap-remote-host %s' from the config file\n", + gsmtap_addr, pcu->gsmtap_remote_host); + talloc_free(pcu->gsmtap_remote_host); + } + pcu->gsmtap_remote_host = talloc_strdup(pcu, gsmtap_addr); + } + + if (pcu->gsmtap_remote_host) { + LOGP(DLGLOBAL, LOGL_NOTICE, + "Setting up GSMTAP Um forwarding to '%s:%u'\n", + pcu->gsmtap_remote_host, GSMTAP_UDP_PORT); + pcu->gsmtap = gsmtap_source_init(pcu->gsmtap_remote_host, + GSMTAP_UDP_PORT, 1); + if (pcu->gsmtap == NULL) { + fprintf(stderr, "Failed during gsmtap_source_init()\n"); + exit(1); + } + gsmtap_source_add_sink(pcu->gsmtap); + } + + rc = telnet_init_default(tall_pcu_ctx, NULL, OSMO_VTY_PORT_PCU); if (rc < 0) { fprintf(stderr, "Error initializing telnet\n"); exit(1); diff --git a/src/pcu_utils.h b/src/pcu_utils.h index e1a8572a..540f7049 100644 --- a/src/pcu_utils.h +++ b/src/pcu_utils.h @@ -10,10 +10,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once #ifdef __cplusplus @@ -71,23 +67,25 @@ static inline unsigned fn_next_block(unsigned fn) return fn % GSM_MAX_FN; } -#ifdef __cplusplus -template <typename T> -inline unsigned int pcu_bitcount(T x) +inline unsigned int pcu_bitcount(unsigned long long x) { - unsigned int count = 0; - for (count = 0; x; count += 1) - x &= x - 1; - - return count; + return __builtin_popcountll(x); } -#endif +/* Trim all 1 bits except the Least Significat Bit, which is kept. + * equivalent of (1 << ffs(u)) / 2 + */ static inline uint8_t pcu_lsb(uint8_t x) { return x & -x; } +#define RFN_MODULUS 42432 +static inline uint16_t fn2rfn(uint32_t fn) +{ + return fn % RFN_MODULUS; +} + /* Used to store a C++ class in a llist used by C code */ struct llist_item { struct llist_head list; /* item used by llist */ diff --git a/src/pcu_vty.c b/src/pcu_vty.c index c85e324b..9de99c82 100644 --- a/src/pcu_vty.c +++ b/src/pcu_vty.c @@ -23,6 +23,7 @@ #include "bts.h" #include "tbf.h" #include "pcu_vty_functions.h" +#include "alloc_algo.h" #define X(x) (1 << x) @@ -70,6 +71,49 @@ static const struct value_string pcu_gsmtap_categ_help[] = { { 0, NULL } }; +DEFUN(cfg_pcu_gsmtap_remote_host, + cfg_pcu_gsmtap_remote_host_cmd, + "gsmtap-remote-host [HOSTNAME]", + "Enable GSMTAP Um logging (see also 'gsmtap-category')\n" + "Remote IP address or hostname ('localhost' if omitted)\n") +{ + osmo_talloc_replace_string(the_pcu, &the_pcu->gsmtap_remote_host, + argc > 0 ? argv[0] : "localhost"); + + if (vty->type != VTY_FILE) + vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_pcu_no_gsmtap_remote_host, + cfg_pcu_no_gsmtap_remote_host_cmd, + "no gsmtap-remote-host", + NO_STR "Disable GSMTAP Um logging\n") +{ + if (the_pcu->gsmtap_remote_host) + TALLOC_FREE(the_pcu->gsmtap_remote_host); + + if (vty->type != VTY_FILE) + vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_pcu_gsmtap_sapi_all, pcucfg_pcu_gsmtap_categ_all_cmd, + "gsmtap-category (enable-all|disable-all)", + "Enable/disable sending of UL/DL messages over GSMTAP\n" + "Enable all kinds of messages (all categories)\n" + "Disable all kinds of messages (all categories)\n") +{ + + if (strcmp(argv[0], "enable-all") == 0) + the_pcu->gsmtap_categ_mask = UINT32_MAX; + else + the_pcu->gsmtap_categ_mask = 0x00; + + return CMD_SUCCESS; +} DEFUN(cfg_pcu_gsmtap_categ, cfg_pcu_gsmtap_categ_cmd, "HIDDEN", "HIDDEN") { @@ -235,14 +279,19 @@ static int config_write_pcu(struct vty *vty) vty_out(vty, " gamma %d%s", the_pcu->vty.gamma * 2, VTY_NEWLINE); if (!the_pcu->vty.dl_tbf_preemptive_retransmission) vty_out(vty, " no dl-tbf-preemptive-retransmission%s", VTY_NEWLINE); + if (the_pcu->vty.msclass_default != PCU_DEFAULT_MSLOT_CLASS) + vty_out(vty, " multislot-class default %u%s", the_pcu->vty.msclass_default, VTY_NEWLINE); if (strcmp(the_pcu->pcu_sock_path, PCU_SOCK_DEFAULT)) vty_out(vty, " pcu-socket %s%s", the_pcu->pcu_sock_path, VTY_NEWLINE); + if (the_pcu->gsmtap_remote_host) + vty_out(vty, " gsmtap-remote-host %s%s", the_pcu->gsmtap_remote_host, VTY_NEWLINE); for (i = 0; i < 32; i++) { - uint32_t cs = ((uint32_t)1 << i); - if (the_pcu->gsmtap_categ_mask & cs) { - vty_out(vty, " gsmtap-category %s%s", - get_value_string(pcu_gsmtap_categ_names, i), VTY_NEWLINE); + if (the_pcu->gsmtap_categ_mask & ((uint32_t)1 << i)) { + const char *category_buf; + if (!(category_buf = get_value_string_or_null(pcu_gsmtap_categ_names, i))) + continue; + vty_out(vty, " gsmtap-category %s%s", category_buf, VTY_NEWLINE); } } @@ -256,11 +305,6 @@ static int config_write_pcu(struct vty *vty) if (the_pcu->vty.ns_priority != -1) vty_out(vty, " gb socket-priority %d%s", the_pcu->vty.ns_priority, VTY_NEWLINE); - if (the_pcu->vty.neigh_ctrl_addr) { - vty_out(vty, " neighbor resolution %s %u%s", - the_pcu->vty.neigh_ctrl_addr, the_pcu->vty.neigh_ctrl_port, VTY_NEWLINE); - } - osmo_tdef_vty_write(vty, the_pcu->T_defs, " timer "); return CMD_SUCCESS; @@ -840,6 +884,19 @@ DEFUN_ATTR(cfg_pcu_no_dl_tbf_preemptive_retransmission, return CMD_SUCCESS; } +DEFUN_ATTR_USRATTR(cfg_pcu_msclass_default, + cfg_pcu_msclass_default_cmd, + CMD_ATTR_HIDDEN, + X(PCU_VTY_ATTR_NEW_TBF), + "multislot-class default <1-45>", + "MultiSlot Class configuration\n" + "Set assumed default MultiSlot Class if unknown during TBF allocation\n" + "MultiSlot Class number to use as default (default: 12)\n") +{ + the_pcu->vty.msclass_default = atoi(argv[0]); + return CMD_SUCCESS; +} + #define MS_IDLE_TIME_STR "keep an idle MS object alive for the time given\n" DEFUN_DEPRECATED(cfg_pcu_ms_idle_time, cfg_pcu_ms_idle_time_cmd, @@ -1060,26 +1117,6 @@ DEFUN_USRATTR(cfg_pcu_gb_priority, return CMD_SUCCESS; } -DEFUN(cfg_neighbor_resolution, cfg_neighbor_resolution_cmd, - "neighbor resolution " VTY_IPV46_CMD " [<0-65535>]", - "Manage local and remote-BSS neighbor cells\n" - "Connect to Neighbor Resolution Service (CTRL interface) to given ip and port\n" - "IPv4 address to connect to\n" "IPv6 address to connect to\n" - "Port to connect to (default 4248)\n") -{ - vty_out(vty, "%% Warning: The CTRL interface for Neighbor Address Resolution is now deprecated." - "Upgrade osmo-bsc and drop the 'neighbor resolution " VTY_IPV46_CMD " [<0-65535>]' VTY " - "option in order to let osmo-pcu use the new resoluton method using the PCUIF over IPA " - "multiplex, which will work out of the box without required configuration.%s", VTY_NEWLINE); - osmo_talloc_replace_string(the_pcu, &the_pcu->vty.neigh_ctrl_addr, argv[0]); - if (argc > 1) - the_pcu->vty.neigh_ctrl_port = atoi(argv[1]); - else - the_pcu->vty.neigh_ctrl_port = OSMO_CTRL_PORT_BSC_NEIGH; - return CMD_SUCCESS; -} - - DEFUN(show_bts_timer, show_bts_timer_cmd, "show bts-timer " OSMO_TDEF_VTY_ARG_T_OPTIONAL, SHOW_STR "Show BTS controlled timers\n" @@ -1195,8 +1232,8 @@ DEFUN(show_ms_imsi, } static const char pcu_copyright[] = - "Copyright (C) 2012 by Ivan Kluchnikov <kluchnikovi@gmail.com> and \r\n" - " Andreas Eversberg <jolly@eversberg.eu>\r\n" + "Copyright (C) 2012-2013 by Ivan Kluchnikov and Andreas Eversberg\r\n" + "Copyright (C) 2013-2022 by sysmocom - s.f.m.c. GmbH\r\n" "License GNU GPL version 2 or later\r\n" "This is free software: you are free to change and redistribute it.\r\n" "There is NO WARRANTY, to the extent permitted by law.\r\n"; @@ -1292,15 +1329,18 @@ int pcu_vty_init(void) install_element(PCU_NODE, &cfg_pcu_no_dl_tbf_idle_time_cmd); install_element(PCU_NODE, &cfg_pcu_dl_tbf_preemptive_retransmission_cmd); install_element(PCU_NODE, &cfg_pcu_no_dl_tbf_preemptive_retransmission_cmd); + install_element(PCU_NODE, &cfg_pcu_msclass_default_cmd); install_element(PCU_NODE, &cfg_pcu_ms_idle_time_cmd); install_element(PCU_NODE, &cfg_pcu_no_ms_idle_time_cmd); + install_element(PCU_NODE, &cfg_pcu_gsmtap_remote_host_cmd); + install_element(PCU_NODE, &cfg_pcu_no_gsmtap_remote_host_cmd); + install_element(PCU_NODE, &pcucfg_pcu_gsmtap_categ_all_cmd); install_element(PCU_NODE, &cfg_pcu_gsmtap_categ_cmd); install_element(PCU_NODE, &cfg_pcu_no_gsmtap_categ_cmd); install_element(PCU_NODE, &cfg_pcu_sock_cmd); install_element(PCU_NODE, &cfg_pcu_gb_dialect_cmd); install_element(PCU_NODE, &cfg_pcu_gb_ip_dscp_cmd); install_element(PCU_NODE, &cfg_pcu_gb_priority_cmd); - install_element(PCU_NODE, &cfg_neighbor_resolution_cmd); install_element(PCU_NODE, &cfg_pcu_timer_cmd); install_element_ve(&show_bts_stats_cmd); diff --git a/src/pcu_vty_functions.cpp b/src/pcu_vty_functions.cpp index 657e5a16..09a1d774 100644 --- a/src/pcu_vty_functions.cpp +++ b/src/pcu_vty_functions.cpp @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* OsmoBTS VTY interface */ @@ -24,7 +20,6 @@ #include <stdlib.h> #include "pcu_vty_functions.h" #include "bts.h" -#include "gprs_ms_storage.h" #include "gprs_ms.h" #include "cxx_linuxlist.h" #include <llc.h> @@ -44,28 +39,41 @@ extern "C" { #include "coding_scheme.h" } +static uint32_t tbf_state_flags(const struct gprs_rlcmac_tbf *tbf) +{ + const struct gprs_rlcmac_ul_tbf *ul_tbf = tbf_as_ul_tbf_const(tbf); + const struct gprs_rlcmac_dl_tbf *dl_tbf = tbf_as_dl_tbf_const(tbf); + if (ul_tbf) + return ul_tbf->state_fsm.state_flags; + return dl_tbf->state_fsm.state_flags; +} + static void tbf_print_vty_info(struct vty *vty, struct gprs_rlcmac_tbf *tbf) { - gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(tbf); - gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(tbf); + gprs_rlcmac_ul_tbf *ul_tbf = tbf_as_ul_tbf(tbf); + gprs_rlcmac_dl_tbf *dl_tbf = tbf_as_dl_tbf(tbf); + uint32_t state_flags = tbf_state_flags(tbf); + struct GprsMs *ms = tbf_ms(tbf); + const struct gprs_rlcmac_pdch *first_common_ts = ms_first_common_ts(ms); vty_out(vty, "TBF: TFI=%d TLLI=0x%08x (%s) TA=%u DIR=%s IMSI=%s%s", tbf->tfi(), tbf->tlli(), tbf->is_tlli_valid() ? "valid" : "invalid", tbf->ta(), tbf->direction == GPRS_RLCMAC_UL_TBF ? "UL" : "DL", tbf->imsi(), VTY_NEWLINE); - vty_out(vty, " created=%lu state=%08x [CCCH:%u, PACCH:%u] 1st_TS=%d 1st_cTS=%d ctrl_TS=%d MS_CLASS=%d/%d%s", - tbf->created_ts(), tbf->state_fsm.state_flags, - tbf->state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH), - tbf->state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH), - tbf->first_ts, - tbf->first_common_ts, tbf->control_ts, + vty_out(vty, " created=%lu state=%s flags=%08x [CCCH:%u, PACCH:%u] 1st_cTS=%" PRId8 " ctrl_TS=%d MS_CLASS=%d/%d%s", + tbf->created_ts(), tbf->state_name(), + state_flags, + state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH), + state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH), + first_common_ts ? first_common_ts->ts_no : -1, + tbf->control_ts ? tbf->control_ts->ts_no : -1, tbf->ms_class(), - tbf->ms() ? ms_egprs_ms_class(tbf->ms()) : -1, + ms_egprs_ms_class(ms), VTY_NEWLINE); vty_out(vty, " TS_alloc="); for (int i = 0; i < 8; i++) { - bool is_ctrl = tbf->is_control_ts(i); + bool is_ctrl = tbf_is_control_ts(tbf, tbf->pdch[i]); if (tbf->pdch[i]) vty_out(vty, "%d%s ", i, is_ctrl ? "!" : ""); } @@ -79,11 +87,10 @@ static void tbf_print_vty_info(struct vty *vty, struct gprs_rlcmac_tbf *tbf) ul_tbf->window_size(), win->v_q(), win->v_r()); vty_out(vty, "%s", VTY_NEWLINE); vty_out(vty, " TBF Statistics:%s", VTY_NEWLINE); - if (GPRS == ms_mode(tbf->ms())) { + if (ul_tbf->m_ul_gprs_ctrs) vty_out_rate_ctr_group(vty, " ", ul_tbf->m_ul_gprs_ctrs); - } else { + if (ul_tbf->m_ul_egprs_ctrs) vty_out_rate_ctr_group(vty, " ", ul_tbf->m_ul_egprs_ctrs); - } } if (dl_tbf) { gprs_rlc_dl_window *win = static_cast<gprs_rlc_dl_window *>(dl_tbf->window()); @@ -92,11 +99,10 @@ static void tbf_print_vty_info(struct vty *vty, struct gprs_rlcmac_tbf *tbf) win->window_stalled() ? " STALLED" : ""); vty_out(vty, "%s", VTY_NEWLINE); vty_out_rate_ctr_group(vty, " ", tbf->m_ctrs); - if (GPRS == ms_mode(tbf->ms())) { + if (dl_tbf->m_dl_gprs_ctrs) vty_out_rate_ctr_group(vty, " ", dl_tbf->m_dl_gprs_ctrs); - } else { + if (dl_tbf->m_dl_egprs_ctrs) vty_out_rate_ctr_group(vty, " ", dl_tbf->m_dl_egprs_ctrs); - } } vty_out(vty, "%s%s", VTY_NEWLINE, VTY_NEWLINE); } @@ -106,6 +112,8 @@ int pcu_vty_show_tbf_all(struct vty *vty, struct gprs_rlcmac_bts *bts, uint32_t struct llist_item *iter; const struct gprs_rlcmac_trx *trx; struct gprs_rlcmac_tbf *tbf; + const struct gprs_rlcmac_ul_tbf *ul_tbf; + const struct gprs_rlcmac_dl_tbf *dl_tbf; size_t trx_no; vty_out(vty, "UL TBFs%s", VTY_NEWLINE); @@ -113,7 +121,8 @@ int pcu_vty_show_tbf_all(struct vty *vty, struct gprs_rlcmac_bts *bts, uint32_t trx = &bts->trx[trx_no]; llist_for_each_entry(iter, &trx->ul_tbfs, list) { tbf = (struct gprs_rlcmac_tbf *)iter->entry; - if (tbf->state_fsm.state_flags & flags) + ul_tbf = tbf_as_ul_tbf_const(tbf); + if (ul_tbf->state_fsm.state_flags & flags) tbf_print_vty_info(vty, tbf); } } @@ -123,7 +132,8 @@ int pcu_vty_show_tbf_all(struct vty *vty, struct gprs_rlcmac_bts *bts, uint32_t trx = &bts->trx[trx_no]; llist_for_each_entry(iter, &trx->dl_tbfs, list) { tbf = (struct gprs_rlcmac_tbf *)iter->entry; - if (tbf->state_fsm.state_flags & flags) + dl_tbf = tbf_as_dl_tbf_const(tbf); + if (dl_tbf->state_fsm.state_flags & flags) tbf_print_vty_info(vty, tbf); } } @@ -138,11 +148,16 @@ static int show_ms(struct vty *vty, GprsMs *ms) uint8_t slots; vty_out(vty, "MS TLLI=%08x, IMSI=%s%s", ms_tlli(ms), ms_imsi(ms), VTY_NEWLINE); - vty_out(vty, " Timing advance (TA): %d%s", ms_ta(ms), VTY_NEWLINE); - vty_out(vty, " Coding scheme uplink: %s%s", mcs_name(ms_current_cs_ul(ms)), - VTY_NEWLINE); - vty_out(vty, " Coding scheme downlink: %s%s", mcs_name(ms_current_cs_dl(ms, ms_mode(ms))), - VTY_NEWLINE); + if (osmo_timer_pending(&ms->release_timer)) { + struct timeval tv_now, tv_res1, tv_res2; + osmo_gettimeofday(&tv_now, NULL); + timersub(&tv_now, &ms->tv_idle_start, &tv_res1); + osmo_timer_remaining(&ms->release_timer, &tv_now, &tv_res2); + vty_out(vty, " State: IDLE for %lus, release in %lus%s", + tv_res1.tv_sec, tv_res2.tv_sec, VTY_NEWLINE); + } else { + vty_out(vty, " State: ACTIVE%s", VTY_NEWLINE); + } vty_out(vty, " Mode: %s%s", mode_name(ms_mode(ms)), VTY_NEWLINE); vty_out(vty, " MS class: %d%s", ms_ms_class(ms), VTY_NEWLINE); vty_out(vty, " EGPRS MS class: %d%s", ms_egprs_ms_class(ms), VTY_NEWLINE); @@ -150,12 +165,17 @@ static int show_ms(struct vty *vty, GprsMs *ms) slots = ms_current_pacch_slots(ms); for (int i = 0; i < 8; i++) if (slots & (1 << i)) - vty_out(vty, "%d ", i); + vty_out(vty, "TS%d ", i); vty_out(vty, "%s", VTY_NEWLINE); - vty_out(vty, " LLC queue length: %zd%s", llc_queue_size(ms_llc_queue(ms)), + vty_out(vty, " DL LLC queue length: %zd%s", llc_queue_size(ms_llc_queue(ms)), VTY_NEWLINE); - vty_out(vty, " LLC queue octets: %zd%s", llc_queue_octets(ms_llc_queue(ms)), + vty_out(vty, " DL LLC queue octets: %zd%s", llc_queue_octets(ms_llc_queue(ms)), VTY_NEWLINE); + vty_out(vty, " DL Coding Scheme: %s%s", mcs_name(ms_current_cs_dl(ms, ms_mode(ms))), + VTY_NEWLINE); + vty_out(vty, " UL Coding Scheme: %s%s", mcs_name(ms_current_cs_ul(ms)), + VTY_NEWLINE); + vty_out(vty, " Timing advance (TA): %d%s", ms_ta(ms), VTY_NEWLINE); if (ms->l1_meas.have_rssi) vty_out(vty, " RSSI: %d dBm%s", ms->l1_meas.rssi, VTY_NEWLINE); @@ -172,7 +192,7 @@ static int show_ms(struct vty *vty, GprsMs *ms) vty_out(vty, " Downlink NACK rate: %d %%%s", ms_nack_rate_dl(ms), VTY_NEWLINE); if (ms->l1_meas.have_ms_rx_qual) - vty_out(vty, " MS RX quality: %d %%%s", + vty_out(vty, " MS Rx quality: %d %%%s", ms->l1_meas.ms_rx_qual, VTY_NEWLINE); if (ms->l1_meas.have_ms_c_value) vty_out(vty, " MS C value: %d dB%s", @@ -186,25 +206,25 @@ static int show_ms(struct vty *vty, GprsMs *ms) i, ms->l1_meas.ts[i].ms_i_level, VTY_NEWLINE); } if (ms_ul_tbf(ms)) - vty_out(vty, " Uplink TBF: TFI=%d, state=%s%s", + vty_out(vty, " UL TBF: TFI=%d, state=%s%s", ms_ul_tbf(ms)->tfi(), ms_ul_tbf(ms)->state_name(), VTY_NEWLINE); if (ms_dl_tbf(ms)) { - vty_out(vty, " Downlink TBF: TFI=%d, state=%s%s", + vty_out(vty, " DL TBF: TFI=%d, state=%s%s", ms_dl_tbf(ms)->tfi(), ms_dl_tbf(ms)->state_name(), VTY_NEWLINE); - vty_out(vty, " Current DL Throughput: %d Kbps %s", + vty_out(vty, " Current DL Throughput: %d Kbps%s", ms_dl_tbf(ms)->m_bw.dl_throughput, VTY_NEWLINE); } llist_for_each_entry(i_tbf, &ms->old_tbfs, list) { struct gprs_rlcmac_tbf *tbf = (struct gprs_rlcmac_tbf *)i_tbf->entry; - vty_out(vty, " Old %-19s TFI=%d, state=%s%s", + vty_out(vty, " Old %s TBF: TFI=%d, state=%s%s", tbf_direction(tbf) == GPRS_RLCMAC_UL_TBF ? - "Uplink TBF:" : "Downlink TBF:", + "UL" : "DL", tbf->tfi(), tbf->state_name(), VTY_NEWLINE); @@ -218,8 +238,8 @@ int pcu_vty_show_ms_all(struct vty *vty, struct gprs_rlcmac_bts *bts) { struct llist_head *tmp; - llist_for_each(tmp, bts_ms_store(bts)->ms_list()) { - GprsMs *ms_iter = llist_entry(tmp, typeof(*ms_iter), list); + llist_for_each(tmp, &bts->ms_list) { + struct GprsMs *ms_iter = llist_entry(tmp, typeof(*ms_iter), list); show_ms(vty, ms_iter); } @@ -229,7 +249,7 @@ int pcu_vty_show_ms_all(struct vty *vty, struct gprs_rlcmac_bts *bts) int pcu_vty_show_ms_by_tlli(struct vty *vty, struct gprs_rlcmac_bts *bts, uint32_t tlli) { - GprsMs *ms = bts_ms_store(bts)->get_ms(tlli); + struct GprsMs *ms = bts_get_ms_by_tlli(bts, tlli, GSM_RESERVED_TMSI); if (!ms) { vty_out(vty, "Unknown TLLI %08x.%s", tlli, VTY_NEWLINE); return CMD_WARNING; @@ -241,7 +261,7 @@ int pcu_vty_show_ms_by_tlli(struct vty *vty, struct gprs_rlcmac_bts *bts, int pcu_vty_show_ms_by_imsi(struct vty *vty, struct gprs_rlcmac_bts *bts, const char *imsi) { - GprsMs *ms = bts_ms_store(bts)->get_ms(0, 0, imsi); + struct GprsMs *ms = bts_get_ms_by_imsi(bts, imsi); if (!ms) { vty_out(vty, "Unknown IMSI '%s'.%s", imsi, VTY_NEWLINE); return CMD_WARNING; @@ -269,7 +289,7 @@ int pcu_vty_show_bts_pdch(struct vty *vty, const struct gprs_rlcmac_bts *bts) for (ts_nr = 0; ts_nr < ARRAY_SIZE(trx->pdch); ts_nr++) { const struct gprs_rlcmac_pdch *pdch = &trx->pdch[ts_nr]; - vty_out(vty, " TS%u: PDCH %s, %u UL TBFs, %u DL TBFs%s", pdch->ts_no, + vty_out(vty, " TS%u: PDCH %s, %u DL TBFs, %u UL TBFs%s", pdch->ts_no, pdch->is_enabled() ? "enabled" : "disabled", pdch->num_tbfs(GPRS_RLCMAC_DL_TBF), pdch->num_tbfs(GPRS_RLCMAC_UL_TBF), VTY_NEWLINE); diff --git a/src/pcu_vty_functions.h b/src/pcu_vty_functions.h index a68df27c..172dffcc 100644 --- a/src/pcu_vty_functions.h +++ b/src/pcu_vty_functions.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once diff --git a/src/osmobts_sock.c b/src/pcuif_sock.c index 91b62a00..f8c0ecc0 100644 --- a/src/osmobts_sock.c +++ b/src/pcuif_sock.c @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <stdio.h> @@ -34,6 +30,7 @@ #include <pcu_l1_if.h> +#include <pcu_l1_if_phy.h> #include <gprs_debug.h> #include <gprs_bssgp_pcu.h> #include <osmocom/pcu/pcuif_proto.h> @@ -41,8 +38,6 @@ #include <tbf.h> #include <pdch.h> -int l1if_close_pdch(void *obj); - /* * osmo-bts PCU socket functions */ @@ -101,9 +96,9 @@ static void pcu_sock_close(int lost) LOGP(DL1IF, LOGL_NOTICE, "PCU socket has %s connection\n", (lost) ? "LOST" : "closed"); + osmo_fd_unregister(bfd); close(bfd->fd); bfd->fd = -1; - osmo_fd_unregister(bfd); /* flush the queue */ while (!llist_empty(&pcu_sock_state.upqueue)) { @@ -116,7 +111,7 @@ static void pcu_sock_close(int lost) for (trx = 0; trx < 8; trx++) { #ifdef ENABLE_DIRECT_PHY if (bts->trx[trx].fl1h) { - l1if_close_pdch(bts->trx[trx].fl1h); + l1if_close_trx(bts->trx[trx].fl1h); bts->trx[trx].fl1h = NULL; } #endif @@ -143,6 +138,7 @@ static int pcu_sock_read(struct osmo_fd *bfd) if (rc < 0 && errno == EAGAIN) return 0; /* Try again later */ if (rc <= 0) { + LOGP(DL1IF, LOGL_ERROR, "%s: recv() failed with rc=%d errno=%d\n", __func__, rc, errno); pcu_sock_close(1); return -EIO; } @@ -221,7 +217,7 @@ static int pcu_sock_cb(struct osmo_fd *bfd, unsigned int flags) int pcu_l1if_open(void) { int rc; - LOGP(DL1IF, LOGL_INFO, "Opening OsmoPCU L1 interface to OsmoBTS\n"); + LOGP(DL1IF, LOGL_INFO, "Opening OsmoPCU L1 interface v%u to OsmoBTS/OsmoBSC\n", PCU_IF_VERSION); memset(&pcu_sock_state, 0x00, sizeof(pcu_sock_state)); INIT_LLIST_HEAD(&pcu_sock_state.upqueue); diff --git a/src/pdch.cpp b/src/pdch.cpp index 5d8658af..7ff1c7e6 100644 --- a/src/pdch.cpp +++ b/src/pdch.cpp @@ -5,8 +5,8 @@ * 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 + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, @@ -14,7 +14,7 @@ * 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 + * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ @@ -27,7 +27,6 @@ #include <gprs_debug.h> #include <coding_scheme.h> #include <gprs_ms.h> -#include <gprs_ms_storage.h> #include <pcu_l1_if.h> #include <rlc.h> #include <sba.h> @@ -119,18 +118,44 @@ static inline void sched_ul_ass_or_rej(struct gprs_rlcmac_bts *bts, struct gprs_ bts_do_rate_ctr_inc(bts, CTR_CHANNEL_REQUEST_DESCRIPTION); /* This call will register the new TBF with the MS on success */ - gprs_rlcmac_ul_tbf *ul_tbf = tbf_alloc_ul_pacch(bts, tbf->ms(), tbf->trx->trx_no); + gprs_rlcmac_ul_tbf *ul_tbf = ms_new_ul_tbf_assigned_pacch(tbf->ms(), tbf->trx->trx_no); /* schedule uplink assignment or reject */ if (ul_tbf) { - LOGP(DRLCMAC, LOGL_DEBUG, "MS requests UL TBF in ack message, so we provide one:\n"); + LOGPTBFDL(tbf, LOGL_DEBUG, "MS requests UL TBF in ack message, so we provide one:\n"); osmo_fsm_inst_dispatch(tbf->ul_ass_fsm.fi, TBF_UL_ASS_EV_SCHED_ASS, NULL); } else { - LOGP(DRLCMAC, LOGL_DEBUG, "MS requests UL TBF in ack message, so we packet access reject:\n"); + LOGPTBFDL(tbf, LOGL_NOTICE, "MS requests UL TBF in ack message, but alloc failed: send PktAssRej\n"); osmo_fsm_inst_dispatch(tbf->ul_ass_fsm.fi, TBF_UL_ASS_EV_SCHED_ASS_REJ, NULL); } } +/* Make sure the PDCH vanished from the mask of reserved PDCHs for all MS, to + * avoid alloc_algorithm using it. */ +static void pdch_unreserve_all_ms_reserved_slots(struct gprs_rlcmac_pdch *pdch) +{ + struct llist_head *tmp; + uint8_t ts_rm_mask = (~(1 << pdch->ts_no)); + struct gprs_rlcmac_trx *trx = pdch->trx; + + llist_for_each(tmp, &trx->bts->ms_list) { + struct GprsMs *ms = llist_entry(tmp, typeof(*ms), list); + if (ms->current_trx != trx) + continue; + uint8_t old_dl_slots = ms_reserved_dl_slots(ms); + uint8_t old_ul_slots = ms_reserved_ul_slots(ms); + uint8_t new_dl_slots = old_dl_slots & ts_rm_mask; + uint8_t new_ul_slots = old_ul_slots & ts_rm_mask; + if (old_dl_slots == new_dl_slots && old_ul_slots == new_ul_slots) + continue; + ms_set_reserved_slots(ms, trx, new_ul_slots, new_dl_slots); + } + if (pdch->num_reserved(GPRS_RLCMAC_UL_TBF) > 0 || pdch->num_reserved(GPRS_RLCMAC_DL_TBF) > 0) + LOGPDCH(pdch, DRLCMAC, LOGL_ERROR, + "Reserved TS count not zero after unreserving from all current MS in list! UL=%u DL=%u\n", + pdch->num_reserved(GPRS_RLCMAC_UL_TBF), pdch->num_reserved(GPRS_RLCMAC_DL_TBF)); +} + void pdch_init(struct gprs_rlcmac_pdch *pdch, struct gprs_rlcmac_trx *trx, uint8_t ts_nr) { pdch->ts_no = ts_nr; @@ -143,6 +168,7 @@ void pdch_init(struct gprs_rlcmac_pdch *pdch, struct gprs_rlcmac_trx *trx, uint8 void gprs_rlcmac_pdch::enable() { + LOGPDCH(this, DRLCMAC, LOGL_INFO, "PDCH state: %s => enabled\n", m_is_enabled ? "enabled" : "disabled"); OSMO_ASSERT(m_is_enabled == 0); INIT_LLIST_HEAD(&paging_list); @@ -155,6 +181,7 @@ void gprs_rlcmac_pdch::enable() void gprs_rlcmac_pdch::disable() { + LOGPDCH(this, DRLCMAC, LOGL_INFO, "PDCH state: %s => disabled\n", m_is_enabled ? "enabled" : "disabled"); OSMO_ASSERT(m_is_enabled == 1); this->free_resources(); @@ -169,6 +196,8 @@ void gprs_rlcmac_pdch::free_resources() /* kick all TBF on slot */ pdch_free_all_tbf(this); + pdch_unreserve_all_ms_reserved_slots(this); + /* flush all pending paging messages */ while ((pag = dequeue_paging())) talloc_free(pag); @@ -315,30 +344,20 @@ void gprs_rlcmac_pdch::rcv_control_ack(Packet_Control_Acknowledgement_t *packet, poll = pdch_ulc_get_node(ulc, fn); if (!poll || poll->type != PDCH_ULC_NODE_TBF_POLL) { - LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "PACKET CONTROL ACK with " - "unknown FN=%u TLLI=0x%08x (TRX %d TS %d)\n", - fn, tlli, trx_no(), ts_no); - ms = bts_ms_by_tlli(bts(), tlli, GSM_RESERVED_TMSI); + LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "PACKET CONTROL ACK with unknown FN=%u TLLI=0x%08x\n", + fn, tlli); + ms = bts_get_ms_by_tlli(bts(), tlli, GSM_RESERVED_TMSI); if (ms) LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "PACKET CONTROL ACK with " - "unknown TBF corresponds to MS with IMSI %s, TA %d, " - "uTBF (TFI=%d, state=%s), dTBF (TFI=%d, state=%s)\n", - ms_imsi(ms), ms_ta(ms), - ms_ul_tbf(ms) ? ms_ul_tbf(ms)->tfi() : 0, - ms_ul_tbf(ms) ? ms_ul_tbf(ms)->state_name() : "None", - ms_dl_tbf(ms) ? ms_dl_tbf(ms)->tfi() : 0, - ms_dl_tbf(ms) ? ms_dl_tbf(ms)->state_name() : "None"); + "unknown TBF corresponds to %s\n", ms_name(ms)); return; } OSMO_ASSERT(poll->tbf_poll.poll_tbf); tbf = poll->tbf_poll.poll_tbf; reason = poll->tbf_poll.reason; - /* Reset N3101 counter: */ - tbf->n_reset(N3101); - - tbf->update_ms(tlli, GPRS_RLCMAC_UL_TBF); - /* Gather MS from TBF, since it may be NULL or may have been merged during update_ms */ + ms_update_announced_tlli(tbf->ms(), tlli); + /* Gather MS from TBF again, since it may be NULL or may have been merged during ms_update_announced_tlli */ ms = tbf->ms(); LOGPTBF(tbf, LOGL_DEBUG, "FN=%" PRIu32 " Rx Packet Control Ack (reason=%s)\n", @@ -346,7 +365,7 @@ void gprs_rlcmac_pdch::rcv_control_ack(Packet_Control_Acknowledgement_t *packet, switch (reason) { case PDCH_ULC_POLL_UL_ACK: - ul_tbf = as_ul_tbf(tbf); + ul_tbf = tbf_as_ul_tbf(tbf); OSMO_ASSERT(ul_tbf); if (!tbf_ul_ack_exp_ctrl_ack(ul_tbf, fn, ts_no)) { LOGPTBF(tbf, LOGL_NOTICE, "FN=%d, TS=%d (curr FN %d): POLL_UL_ACK not expected!\n", @@ -355,9 +374,12 @@ void gprs_rlcmac_pdch::rcv_control_ack(Packet_Control_Acknowledgement_t *packet, } pdch_ulc_release_fn(ulc, fn); osmo_fsm_inst_dispatch(ul_tbf->ul_ack_fsm.fi, TBF_UL_ACK_EV_RX_CTRL_ACK, NULL); - /* We only set polling on final UL ACK/NACK */ - LOGPTBF(tbf, LOGL_DEBUG, "[UPLINK] END\n"); - osmo_fsm_inst_dispatch(ul_tbf->state_fsm.fi, TBF_EV_FINAL_UL_ACK_CONFIRMED, NULL); + /* We only set polling on final UL ACK/NACK, something is wrong... */ + if (tbf_state(tbf) == TBF_ST_FINISHED) + osmo_fsm_inst_dispatch(ul_tbf->state_fi, TBF_EV_FINAL_UL_ACK_CONFIRMED, (void*)false); + /* ul_tbf is freed here! */ + else + LOGPTBFUL(ul_tbf, LOGL_ERROR, "Received POLL_UL_ACK for UL TBF in unexpected state!\n"); return; case PDCH_ULC_POLL_UL_ASS: @@ -369,8 +391,8 @@ void gprs_rlcmac_pdch::rcv_control_ack(Packet_Control_Acknowledgement_t *packet, } LOGPTBF(tbf, LOGL_DEBUG, "[DOWNLINK] UPLINK ASSIGNED\n"); pdch_ulc_release_fn(ulc, fn); - /* reset N3105 */ - tbf->n_reset(N3105); + if (tbf_direction(tbf) == GPRS_RLCMAC_DL_TBF) + tbf->n_reset(N3105); osmo_fsm_inst_dispatch(tbf->ul_ass_fsm.fi, TBF_UL_ASS_EV_RX_ASS_CTRL_ACK, NULL); new_tbf = ms_ul_tbf(ms); @@ -379,16 +401,13 @@ void gprs_rlcmac_pdch::rcv_control_ack(Packet_Control_Acknowledgement_t *packet, "TBF is gone TLLI=0x%08x\n", tlli); return; } - if (tbf->state_is(TBF_ST_WAIT_RELEASE) && - tbf->direction == new_tbf->direction) - tbf_free(tbf); - osmo_fsm_inst_dispatch(new_tbf->state_fsm.fi, TBF_EV_ASSIGN_ACK_PACCH, NULL); + osmo_fsm_inst_dispatch(new_tbf->state_fi, TBF_EV_ASSIGN_ACK_PACCH, NULL); /* there might be LLC packets waiting in the queue, but the DL * TBF might have been released while the UL TBF has been * established */ if (ms_need_dl_tbf(new_tbf->ms())) - new_tbf->establish_dl_tbf_on_pacch(); + ms_new_dl_tbf_assigned_on_pacch(new_tbf->ms(), new_tbf); return; case PDCH_ULC_POLL_DL_ASS: @@ -400,8 +419,8 @@ void gprs_rlcmac_pdch::rcv_control_ack(Packet_Control_Acknowledgement_t *packet, } LOGPTBF(tbf, LOGL_DEBUG, "[UPLINK] DOWNLINK ASSIGNED\n"); pdch_ulc_release_fn(ulc, fn); - /* reset N3105 */ - tbf->n_reset(N3105); + if (tbf_direction(tbf) == GPRS_RLCMAC_DL_TBF) + tbf->n_reset(N3105); osmo_fsm_inst_dispatch(tbf->dl_ass_fsm.fi, TBF_DL_ASS_EV_RX_ASS_CTRL_ACK, NULL); new_tbf = ms_dl_tbf(ms); @@ -410,11 +429,12 @@ void gprs_rlcmac_pdch::rcv_control_ack(Packet_Control_Acknowledgement_t *packet, "TBF is gone TLLI=0x%08x\n", tlli); return; } - if (tbf->state_is(TBF_ST_WAIT_RELEASE) && - tbf->direction == new_tbf->direction) + if ((tbf->state_is(TBF_ST_WAIT_RELEASE) || + tbf->state_is(TBF_ST_WAIT_REUSE_TFI)) && + tbf->direction == new_tbf->direction) tbf_free(tbf); - osmo_fsm_inst_dispatch(new_tbf->state_fsm.fi, TBF_EV_ASSIGN_ACK_PACCH, NULL); + osmo_fsm_inst_dispatch(new_tbf->state_fi, TBF_EV_ASSIGN_ACK_PACCH, NULL); return; case PDCH_ULC_POLL_CELL_CHG_CONTINUE: @@ -467,16 +487,13 @@ void gprs_rlcmac_pdch::rcv_control_dl_ack_nack(Packet_Downlink_Ack_Nack_t *ack_n return; } OSMO_ASSERT(poll->tbf_poll.poll_tbf); - tbf = as_dl_tbf(poll->tbf_poll.poll_tbf); + tbf = tbf_as_dl_tbf(poll->tbf_poll.poll_tbf); if (tbf->tfi() != tfi) { LOGPTBFDL(tbf, LOGL_NOTICE, "PACKET DOWNLINK ACK with wrong TFI=%d, ignoring!\n", tfi); return; } - /* Reset N3101 counter: */ - tbf->n_reset(N3101); - pdch_ulc_release_fn(ulc, fn); LOGPTBF(tbf, LOGL_DEBUG, "RX: [PCU <- BTS] Packet Downlink Ack/Nack\n"); @@ -537,15 +554,13 @@ void gprs_rlcmac_pdch::rcv_control_egprs_dl_ack_nack(EGPRS_PD_AckNack_t *ack_nac return; } OSMO_ASSERT(poll->tbf_poll.poll_tbf); - tbf = as_dl_tbf(poll->tbf_poll.poll_tbf); + tbf = tbf_as_dl_tbf(poll->tbf_poll.poll_tbf); if (tbf->tfi() != tfi) { LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "EGPRS PACKET DOWNLINK ACK with " "wrong TFI=%d, ignoring!\n", tfi); return; } - /* Reset N3101 counter: */ - tbf->n_reset(N3101); pdch_ulc_release_fn(ulc, fn); LOGPTBF(tbf, LOGL_DEBUG, @@ -611,157 +626,205 @@ void gprs_rlcmac_pdch::rcv_control_egprs_dl_ack_nack(EGPRS_PD_AckNack_t *ack_nac void gprs_rlcmac_pdch::rcv_resource_request(Packet_Resource_Request_t *request, uint32_t fn, struct pcu_l1_meas *meas) { - struct gprs_rlcmac_sba *sba; int rc; + struct gprs_rlcmac_bts *bts = trx->bts; + struct pdch_ulc_node *item; + char buf[128]; + struct GprsMs *ms = NULL; + struct gprs_rlcmac_sba *sba; + struct gprs_rlcmac_dl_tbf *dl_tbf = NULL; + struct gprs_rlcmac_ul_tbf *ul_tbf = NULL; + struct gprs_rlcmac_ul_tbf *new_ul_tbf = NULL; - if (request->ID.UnionType) { - struct gprs_rlcmac_ul_tbf *ul_tbf = NULL; - struct pdch_ulc_node *item; - uint32_t tlli = request->ID.u.TLLI; - GprsMs *ms = bts_ms_by_tlli(bts(), tlli, GSM_RESERVED_TMSI); + if (!(item = pdch_ulc_get_node(ulc, fn))) { + LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "FN=%u PKT RESOURCE REQ: " + "UL block not reserved\n", fn); + return; + } + + /* First gather MS from TLLI/DL_TFI/UL_TFI:*/ + if (request->ID.UnionType) { /* ID_TYPE = TLLI */ + uint32_t tlli = request->ID.u.TLLI; + ms = bts_get_ms_by_tlli(bts, tlli, GSM_RESERVED_TMSI); if (!ms) { - ms = bts_alloc_ms(bts(), 0, 0); /* ms class updated later */ + /* A reference is already hold immediately below in all cases, no + * need to hold and extra one, pass use_ref=NULL: */ + ms = ms_alloc(bts, NULL); ms_set_tlli(ms, tlli); } + } else if (request->ID.u.Global_TFI.UnionType) { /* ID_TYPE = DL_TFI */ + int8_t tfi = request->ID.u.Global_TFI.u.DOWNLINK_TFI; + dl_tbf = bts_dl_tbf_by_tfi(bts, tfi, trx_no(), ts_no); + if (!dl_tbf) { + LOGP(DRLCMAC, LOGL_NOTICE, "PACKET RESOURCE REQ unknown downlink TFI=%d\n", tfi); + return; + } + ms = tbf_ms(dl_tbf_as_tbf(dl_tbf)); + dl_tbf = NULL; + } else { /* ID_TYPE = UL_TFI */ + int8_t tfi = request->ID.u.Global_TFI.u.UPLINK_TFI; + ul_tbf = bts_ul_tbf_by_tfi(bts, tfi, trx_no(), ts_no); + if (!ul_tbf) { + LOGP(DRLCMAC, LOGL_NOTICE, "PACKET RESOURCE REQ unknown uplink TFI=%d\n", tfi); + return; + } + ms = tbf_ms(ul_tbf_as_tbf(ul_tbf)); + ul_tbf = NULL; + } - /* Keep the ms, even if it gets idle temporarily */ - ms_ref(ms); - if (!(item = pdch_ulc_get_node(ulc, fn))) { - LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "FN=%u PKT RESOURCE REQ: " - "UL block not reserved\n", fn); - goto return_unref; - } + /* Here ms is available (non-null). Keep the ms, even if it gets idle + * temporarily, in order to avoid it being freed if we free any of its + * resources (TBF). */ + OSMO_ASSERT(ms); + ms_ref(ms, __func__); - switch (item->type) { - case PDCH_ULC_NODE_TBF_USF: - /* Is it actually valid for an MS to send a PKT Res Req during USF? */ - ul_tbf = item->tbf_usf.ul_tbf; + + switch (item->type) { + case PDCH_ULC_NODE_TBF_USF: + /* TS 44.060 to 8.1.1.1.2: An MS can send a PKT Res Req during USF, for instance to change its Radio Priority. */ + ul_tbf = item->tbf_usf.ul_tbf; + if (tbf_ms(ul_tbf_as_tbf(ul_tbf)) != ms) { LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "FN=%u PKT RESOURCE REQ: " - "Unexpectedly received, waiting USF of %s\n", - fn, tbf_name(item->tbf_usf.ul_tbf)); - /* Ignore it, let common path expire related ULC entry */ + "Received from unexpected %s vs exp %s\n", fn, + ms_name_buf(ms, buf, sizeof(buf)), ms_name(tbf_ms(ul_tbf_as_tbf(ul_tbf)))); + /* let common path expire the poll */ goto return_unref; - case PDCH_ULC_NODE_SBA: - sba = item->sba.sba; - LOGPDCH(this, DRLCMAC, LOGL_DEBUG, "FN=%u PKT RESOURCE REQ: " - "MS requests UL TBF throguh SBA\n", fn); - ms_set_ta(ms, sba->ta); - sba_free(sba); - break; - case PDCH_ULC_NODE_TBF_POLL: - if (item->tbf_poll.poll_tbf->direction != GPRS_RLCMAC_UL_TBF) { - LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "FN=%u PKT RESOURCE REQ: " - "Unexpectedly received for DL TBF %s\n", fn, - tbf_name(item->tbf_poll.poll_tbf)); - /* let common path expire the poll */ - goto return_unref; - } - ul_tbf = (struct gprs_rlcmac_ul_tbf *)item->tbf_poll.poll_tbf; - if (item->tbf_poll.reason != PDCH_ULC_POLL_UL_ACK) { - LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "FN=%u PKT RESOURCE REQ: " - "Unexpectedly received, waiting for poll reason %d\n", - fn, item->tbf_poll.reason); - /* let common path expire the poll */ - goto return_unref; - } - if (ul_tbf != ms_ul_tbf(ms)) { - LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "FN=%u PKT RESOURCE REQ: " - "Unexpected TLLI 0x%08x received vs exp 0x%08x\n", - fn, tlli, ul_tbf->tlli()); - /* let common path expire the poll */ - goto return_unref; - } - /* 3GPP TS 44.060 $ 9.3.3.3 */ - LOGPTBFUL(ul_tbf, LOGL_DEBUG, "FN=%u PKT RESOURCE REQ: " - "MS requests reuse of finished UL TBF in RRBP " - "block of final UL ACK/NACK\n", fn); - ul_tbf->n_reset(N3103); - pdch_ulc_release_node(ulc, item); - rc = osmo_fsm_inst_dispatch(ul_tbf->state_fsm.fi, TBF_EV_FINAL_UL_ACK_CONFIRMED, NULL); - if (rc) { - /* FSM failed handling, get rid of previous finished UL TBF before providing a new one */ - LOGPTBFUL(ul_tbf, LOGL_NOTICE, - "Got PACKET RESOURCE REQ while TBF not finished, killing pending UL TBF\n"); - tbf_free(ul_tbf); - } /* else: ul_tbf has been freed by state_fsm */ + } + LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "FN=%u PKT RESOURCE REQ: " + "MS requests UL TBF upgrade through USF of %s\n", + fn, tbf_name(item->tbf_usf.ul_tbf)); + pdch_ulc_release_node(ulc, item); + /* FIXME OS#4947: Currenty we free the UL TBF and create a new one below in order to trigger the Pkt UL Ass. + * We should instead keep the same UL TBF working and upgrading it (modify ul_tbf->ul_ass_fsm.fi to + * handle the PktUlAss + PktCtrlAck). + * This is required by spec, and MS are know to continue using the TBF (due to delay in between DL and + * UL scheduling). + * TS 44.060 to 8.1.1.1.2: + * "If the mobile station or the network does not support multiple TBF procedures, then after the + * transmission of the PACKET RESOURCE REQUEST message with the reason for changing PFI, the radio + * priority or peak throughput class of an assigned uplink TBF the mobile station continue to use the + * currently assigned uplink TBF assuming that the requested radio priority or peak throughput class is + * already assigned to that TBF." + */ + tbf_free(ul_tbf); + ul_tbf = NULL; + break; + case PDCH_ULC_NODE_SBA: + sba = item->sba.sba; + LOGPDCH(this, DRLCMAC, LOGL_DEBUG, "FN=%u PKT RESOURCE REQ: " + "MS requests UL TBF throguh SBA\n", fn); + ms_set_ta(ms, sba->ta); + sba_free(sba); + /* If MS identified by TLLI sent us a PktResReq through SBA, it means it came + * from CCCH, so it's for sure not using previous UL BF; drop it if it still + * exits on our end: + */ + if ((ul_tbf = ms_ul_tbf(ms))) { + /* Get rid of previous finished UL TBF before providing a new one */ + LOGPTBFUL(ul_tbf, LOGL_NOTICE, + "Got PACKET RESOURCE REQ while TBF not finished, killing pending UL TBF\n"); + tbf_free(ul_tbf); ul_tbf = NULL; - break; - default: - OSMO_ASSERT(0); } - - /* Here ul_tbf is NULL: - * - SBA case: no previous TBF) and in - * - POLL case: PktResReq is a final ACk confirmation and ul_tbf was freed + /* Similarly, it is for sure not using any DL-TBF. We still may have some because + * we were trying to assign a DL-TBF over CCCH when the MS proactively requested + * for a UL-TBF. In that case we'll need to re-assigna new DL-TBF through PACCH when + * contention resolution is done: */ - - if (request->Exist_MS_Radio_Access_capability2) { - uint8_t ms_class, egprs_ms_class; - ms_class = get_ms_class_by_capability(&request->MS_Radio_Access_capability2); - egprs_ms_class = get_egprs_ms_class_by_capability(&request->MS_Radio_Access_capability2); - if (ms_class) - ms_set_ms_class(ms, ms_class); - if (egprs_ms_class) - ms_set_egprs_ms_class(ms, egprs_ms_class); + if ((dl_tbf = ms_dl_tbf(ms))) { + LOGPTBFDL(dl_tbf, LOGL_NOTICE, + "Got PACKET RESOURCE REQ while DL-TBF pending, killing it\n"); + tbf_free(dl_tbf); + dl_tbf = NULL; } - - ul_tbf = tbf_alloc_ul_pacch(bts(), ms, trx_no()); - if (!ul_tbf) { - handle_tbf_reject(bts(), ms, trx_no(), ts_no); + break; + case PDCH_ULC_NODE_TBF_POLL: + if (item->tbf_poll.poll_tbf->direction != GPRS_RLCMAC_UL_TBF) { + LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "FN=%u PKT RESOURCE REQ: " + "Unexpectedly received for DL TBF %s\n", fn, + tbf_name(item->tbf_poll.poll_tbf)); + /* let common path expire the poll */ goto return_unref; } - - /* Set control TS to the TS where this PktResReq was received, - * which in practice happens to be the control_ts from the - * previous UL-TBF or SBA. When CTRL ACK is received as RRBP of the Pkt - * UL Ass scheduled below, then TBF_EV_ASSIGN_ACK_PACCH will be - * sent to tbf_fsm which will call tbf_assign_control_ts(), - * effectively setting back control_ts to - * tbf->initial_common_ts. */ - LOGPTBF(ul_tbf, LOGL_DEBUG, "change control TS %d -> %d until assignment is complete.\n", - ul_tbf->control_ts, ts_no); - - ul_tbf->control_ts = ts_no; - /* schedule uplink assignment */ - osmo_fsm_inst_dispatch(ul_tbf->ul_ass_fsm.fi, TBF_UL_ASS_EV_SCHED_ASS, NULL); - - /* get measurements */ - get_meas(meas, request); - ms_update_l1_meas(ul_tbf->ms(), meas); -return_unref: - ms_unref(ms); - return; + ul_tbf = tbf_as_ul_tbf(item->tbf_poll.poll_tbf); + if (item->tbf_poll.reason != PDCH_ULC_POLL_UL_ACK) { + LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "FN=%u PKT RESOURCE REQ: " + "Unexpectedly received, waiting for poll reason %d\n", + fn, item->tbf_poll.reason); + /* let common path expire the poll */ + goto return_unref; + } + if (tbf_ms(ul_tbf_as_tbf(ul_tbf)) != ms) { + LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "FN=%u PKT RESOURCE REQ: " + "Received from unexpected %s vs exp %s\n", fn, + ms_name_buf(ms, buf, sizeof(buf)), ms_name(tbf_ms(ul_tbf_as_tbf(ul_tbf)))); + /* let common path expire the poll */ + goto return_unref; + } + /* 3GPP TS 44.060 $ 9.3.3.3 */ + LOGPTBFUL(ul_tbf, LOGL_DEBUG, "FN=%u PKT RESOURCE REQ: " + "MS requests reuse of finished UL TBF in RRBP " + "block of final UL ACK/NACK\n", fn); + ul_tbf->n_reset(N3103); + pdch_ulc_release_node(ulc, item); + rc = osmo_fsm_inst_dispatch(ul_tbf->state_fi, TBF_EV_FINAL_UL_ACK_CONFIRMED, (void*)true); + if (rc) { + /* FSM failed handling, get rid of previous finished UL TBF before providing a new one */ + LOGPTBFUL(ul_tbf, LOGL_NOTICE, + "Got PACKET RESOURCE REQ while TBF not finished, killing pending UL TBF\n"); + tbf_free(ul_tbf); + } /* else: ul_tbf has been freed by state_fsm */ + ul_tbf = NULL; + break; + default: + OSMO_ASSERT(0); } - if (request->ID.u.Global_TFI.UnionType) { - struct gprs_rlcmac_dl_tbf *dl_tbf; - int8_t tfi = request->ID.u.Global_TFI.u.DOWNLINK_TFI; - dl_tbf = bts_dl_tbf_by_tfi(bts(), tfi, trx_no(), ts_no); - if (!dl_tbf) { - LOGP(DRLCMAC, LOGL_NOTICE, "PACKET RESOURCE REQ unknown downlink TFI=%d\n", tfi); - return; - } - LOGPTBFDL(dl_tbf, LOGL_ERROR, - "RX: [PCU <- BTS] FIXME: Packet resource request\n"); + /* Here ul_tbf is NULL: + * - USF case: Previous TBF is explicitly freed (FIXME: this is incorrect, old TBF should be kept in this case) + * - SBA case: no previous TBF + * - POLL case: PktResReq is a final ACk confirmation and ul_tbf was freed + */ - /* Reset N3101 counter: */ - dl_tbf->n_reset(N3101); - } else { - struct gprs_rlcmac_ul_tbf *ul_tbf; - int8_t tfi = request->ID.u.Global_TFI.u.UPLINK_TFI; - ul_tbf = bts_ul_tbf_by_tfi(bts(), tfi, trx_no(), ts_no); - if (!ul_tbf) { - LOGP(DRLCMAC, LOGL_NOTICE, "PACKET RESOURCE REQ unknown uplink TFI=%d\n", tfi); - return; - } - LOGPTBFUL(ul_tbf, LOGL_ERROR, - "RX: [PCU <- BTS] FIXME: Packet resource request\n"); + if (request->Exist_MS_Radio_Access_capability2) { + uint8_t ms_class, egprs_ms_class; + ms_class = get_ms_class_by_capability(&request->MS_Radio_Access_capability2); + egprs_ms_class = get_egprs_ms_class_by_capability(&request->MS_Radio_Access_capability2); + if (ms_class) + ms_set_ms_class(ms, ms_class); + if (egprs_ms_class) + ms_set_egprs_ms_class(ms, egprs_ms_class); + } + + /* get measurements */ + get_meas(meas, request); + ms_update_l1_meas(ms, meas); - /* Reset N3101 counter: */ - ul_tbf->n_reset(N3101); + new_ul_tbf = ms_new_ul_tbf_assigned_pacch(ms, trx_no()); + if (!new_ul_tbf) { + ms_new_ul_tbf_rejected_pacch(ms, this); + goto return_unref; } + + /* Set control TS to the TS where this PktResReq was received, + * which in practice happens to be the control_ts from the + * previous UL-TBF or SBA. When CTRL ACK is received as RRBP of the Pkt + * UL Ass scheduled below, then TBF_EV_ASSIGN_ACK_PACCH will be + * sent to tbf_fsm which will call tbf_assign_control_ts(), + * effectively setting back control_ts to tbf->initial_common_ts. + */ + LOGPTBF(new_ul_tbf, LOGL_INFO, "Change control TS %s -> %s until assignment is complete.\n", + new_ul_tbf->control_ts ? pdch_name_buf(new_ul_tbf->control_ts, buf, sizeof(buf)) : "(none)", + pdch_name(this)); + + new_ul_tbf->control_ts = this; + /* schedule uplink assignment */ + osmo_fsm_inst_dispatch(new_ul_tbf->ul_ass_fsm.fi, TBF_UL_ASS_EV_SCHED_ASS, NULL); +return_unref: + ms_unref(ms, __func__); + return; } void gprs_rlcmac_pdch::rcv_measurement_report(Packet_Measurement_Report_t *report, uint32_t fn) @@ -769,13 +832,16 @@ void gprs_rlcmac_pdch::rcv_measurement_report(Packet_Measurement_Report_t *repor struct gprs_rlcmac_sba *sba; struct pdch_ulc_node *poll; GprsMs *ms; + bool ms_allocated = false; - ms = bts_ms_by_tlli(bts(), report->TLLI, GSM_RESERVED_TMSI); + ms = bts_get_ms_by_tlli(bts(), report->TLLI, GSM_RESERVED_TMSI); if (!ms) { LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "MS send measurement " "but TLLI 0x%08x is unknown\n", report->TLLI); - ms = bts_alloc_ms(bts(), 0, 0); + ms = ms_alloc(bts(), __func__); ms_set_tlli(ms, report->TLLI); + /* Track that we need to free ref at the end: */ + ms_allocated = true; } if ((poll = pdch_ulc_get_node(ulc, fn))) { switch (poll->type) { @@ -796,6 +862,8 @@ void gprs_rlcmac_pdch::rcv_measurement_report(Packet_Measurement_Report_t *repor } } gprs_rlcmac_meas_rep(ms, report); + if (ms_allocated) + ms_unref(ms, __func__); } void gprs_rlcmac_pdch::rcv_cell_change_notification(Packet_Cell_Change_Notification_t *notif, @@ -896,9 +964,6 @@ free_ret: int gprs_rlcmac_pdch::rcv_block(uint8_t *data, uint8_t len, uint32_t fn, struct pcu_l1_meas *meas) { - /* First of all, update TDMA clock: */ - bts_set_current_frame_number(trx->bts, fn); - /* No successfully decoded UL block was received during this FN: */ if (len == 0) return 0; @@ -1056,12 +1121,12 @@ gprs_rlcmac_tbf *gprs_rlcmac_pdch::tbf_from_list_by_tfi( gprs_rlcmac_ul_tbf *gprs_rlcmac_pdch::ul_tbf_by_tfi(uint8_t tfi) { - return as_ul_tbf(tbf_by_tfi(tfi, GPRS_RLCMAC_UL_TBF)); + return tbf_as_ul_tbf(tbf_by_tfi(tfi, GPRS_RLCMAC_UL_TBF)); } gprs_rlcmac_dl_tbf *gprs_rlcmac_pdch::dl_tbf_by_tfi(uint8_t tfi) { - return as_dl_tbf(tbf_by_tfi(tfi, GPRS_RLCMAC_DL_TBF)); + return tbf_as_dl_tbf(tbf_by_tfi(tfi, GPRS_RLCMAC_DL_TBF)); } /* lookup TBF Entity (by TFI) */ @@ -1116,7 +1181,7 @@ void gprs_rlcmac_pdch::attach_tbf(gprs_rlcmac_tbf *tbf) num_tbfs_update(tbf, true); if (tbf->direction == GPRS_RLCMAC_UL_TBF) { - ul_tbf = as_ul_tbf(tbf); + ul_tbf = tbf_as_ul_tbf(tbf); m_assigned_usf |= 1 << ul_tbf->m_usf[ts_no]; } m_assigned_tfi[tbf->direction] |= 1UL << tbf->tfi(); @@ -1132,6 +1197,11 @@ void gprs_rlcmac_pdch::detach_tbf(gprs_rlcmac_tbf *tbf) { gprs_rlcmac_ul_tbf *ul_tbf; + LOGPDCH(this, DRLCMAC, LOGL_INFO, "Detaching %s, %d TBFs, " + "USFs = %02x, TFIs = %08x.\n", + tbf->name(), num_tbfs(tbf->direction), + m_assigned_usf, m_assigned_tfi[tbf->direction]); + if (tbf->is_egprs_enabled()) { OSMO_ASSERT(m_num_tbfs_egprs[tbf->direction] > 0); } else { @@ -1140,18 +1210,13 @@ void gprs_rlcmac_pdch::detach_tbf(gprs_rlcmac_tbf *tbf) num_tbfs_update(tbf, false); if (tbf->direction == GPRS_RLCMAC_UL_TBF) { - ul_tbf = as_ul_tbf(tbf); + ul_tbf = tbf_as_ul_tbf(tbf); m_assigned_usf &= ~(1 << ul_tbf->m_usf[ts_no]); } m_assigned_tfi[tbf->direction] &= ~(1UL << tbf->tfi()); m_tbfs[tbf->direction][tbf->tfi()] = NULL; pdch_ulc_release_tbf(ulc, tbf); - - LOGPDCH(this, DRLCMAC, LOGL_INFO, "Detaching %s, %d TBFs, " - "USFs = %02x, TFIs = %08x.\n", - tbf->name(), num_tbfs(tbf->direction), - m_assigned_usf, m_assigned_tfi[tbf->direction]); } bool gprs_rlcmac_pdch::has_gprs_only_tbf_attached() const @@ -1228,8 +1293,8 @@ void pdch_free_all_tbf(struct gprs_rlcmac_pdch *pdch) * PDCH, since they have no TFI assigned (see handle_tbf_reject()). * Get rid of them too: */ llist_for_each_entry_safe(pos, pos2, &pdch->trx->ul_tbfs, list) { - struct gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf((struct gprs_rlcmac_tbf *)pos->entry); - if (ul_tbf->control_ts == pdch->ts_no) + struct gprs_rlcmac_ul_tbf *ul_tbf = tbf_as_ul_tbf((struct gprs_rlcmac_tbf *)pos->entry); + if (ul_tbf->control_ts == pdch) tbf_free(ul_tbf); } } @@ -1243,3 +1308,27 @@ bool pdch_is_enabled(const struct gprs_rlcmac_pdch *pdch) { return pdch->is_enabled(); } + +/* To be called only on enabled PDCHs. Used to gather information on whether the + * PDCH is currently unable to allocate more TBFs due to any resource being + * full. Used by bts_all_pdch_allocated() for counting purposes. */ +bool pdch_is_full(const struct gprs_rlcmac_pdch *pdch) +{ + return pdch->assigned_tfi(GPRS_RLCMAC_UL_TBF) == NO_FREE_TFI || + pdch->assigned_tfi(GPRS_RLCMAC_DL_TBF) == NO_FREE_TFI || + find_free_usf(pdch->assigned_usf()) < 0; +} + +const char *pdch_name(const struct gprs_rlcmac_pdch *pdch) +{ + static char _pdch_name_buf[128]; + return pdch_name_buf(pdch, _pdch_name_buf, sizeof(_pdch_name_buf)); +} + +char *pdch_name_buf(const struct gprs_rlcmac_pdch *pdch, char *buf, size_t buf_size) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buf_size }; + OSMO_STRBUF_PRINTF(sb, "PDCH(bts=%" PRIu8 ",trx=%" PRIu8 ",ts=%" PRIu8 ")", + pdch->trx->bts->nr, pdch->trx->trx_no, pdch->ts_no); + return buf; +} @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -194,11 +190,12 @@ void pdch_init(struct gprs_rlcmac_pdch *pdch, struct gprs_rlcmac_trx *trx, uint8 void pdch_free_all_tbf(struct gprs_rlcmac_pdch *pdch); void pdch_disable(struct gprs_rlcmac_pdch *pdch); bool pdch_is_enabled(const struct gprs_rlcmac_pdch *pdch); +bool pdch_is_full(const struct gprs_rlcmac_pdch *pdch); +const char *pdch_name(const struct gprs_rlcmac_pdch *pdch); +char *pdch_name_buf(const struct gprs_rlcmac_pdch *pdch, char *buf, size_t buf_size); #ifdef __cplusplus } #endif #define LOGPDCH(pdch, category, level, fmt, args...) \ - LOGP(category, level, "PDCH(bts=%" PRIu8 ",trx=%" PRIu8 ",ts=%" PRIu8 ") " fmt, \ - (pdch)->trx->bts->nr, (pdch)->trx->trx_no, (pdch)->ts_no, \ - ## args) + LOGP(category, level, "%s " fmt, pdch_name(pdch), ## args) diff --git a/src/pdch_ul_controller.c b/src/pdch_ul_controller.c index 79a03391..661957bb 100644 --- a/src/pdch_ul_controller.c +++ b/src/pdch_ul_controller.c @@ -12,15 +12,13 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with it program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <unistd.h> #include <talloc.h> +#include <osmocom/gsm/gsm0502.h> + #include "pdch_ul_controller.h" #include "bts.h" #include "sba.h" @@ -47,20 +45,6 @@ const struct value_string pdch_ulc_tbf_poll_reason_names[] = { { 0, NULL } }; -#define GSM_MAX_FN_THRESH (GSM_MAX_FN >> 1) -/* 0: equal, -1: fn1 BEFORE fn2, 1: fn1 AFTER fn2 */ -static inline int fn_cmp(uint32_t fn1, uint32_t fn2) -{ - if (fn1 == fn2) - return 0; - /* FN1 goes before FN2: */ - if ((fn1 < fn2 && (fn2 - fn1) < GSM_MAX_FN_THRESH) || - (fn1 > fn2 && (fn1 - fn2) > GSM_MAX_FN_THRESH)) - return -1; - /* FN1 goes after FN2: */ - return 1; -} - struct pdch_ulc *pdch_ulc_alloc(struct gprs_rlcmac_pdch *pdch, void *ctx) { struct pdch_ulc* ulc; @@ -75,13 +59,14 @@ struct pdch_ulc *pdch_ulc_alloc(struct gprs_rlcmac_pdch *pdch, void *ctx) struct pdch_ulc_node *pdch_ulc_get_node(struct pdch_ulc *ulc, uint32_t fn) { + OSMO_ASSERT(ulc); struct rb_node *node = ulc->tree_root.rb_node; struct pdch_ulc_node *it; int res; while (node) { it = rb_entry(node, struct pdch_ulc_node, node); - res = fn_cmp(it->fn, fn); + res = gsm0502_fncmp(it->fn, fn); if (res > 0) /* it->fn AFTER fn */ node = node->rb_left; else if (res < 0) /* it->fn BEFORE fn */ @@ -161,7 +146,7 @@ int pdch_ulc_get_next_free_rrbp_fn(struct pdch_ulc *ulc, uint32_t fn, uint32_t * } /* Get next free (unreserved) FN which is not located in time before "start_fn" */ -uint32_t pdch_ulc_get_next_free_fn(struct pdch_ulc *ulc, uint32_t start_fn) +uint32_t pdch_ulc_get_next_free_fn(const struct pdch_ulc *ulc, uint32_t start_fn) { struct rb_node *node; struct pdch_ulc_node *it; @@ -170,12 +155,12 @@ uint32_t pdch_ulc_get_next_free_fn(struct pdch_ulc *ulc, uint32_t start_fn) for (node = rb_first(&ulc->tree_root); node; node = rb_next(node)) { it = container_of(node, struct pdch_ulc_node, node); - res = fn_cmp(it->fn, check_fn); + res = gsm0502_fncmp(it->fn, check_fn); if (res > 0) { /* it->fn AFTER check_fn */ /* Next reserved FN is passed check_fn, hence it means check_fn is free */ return check_fn; } - /* if it->fn < check_fn, simply continue iterating, we want to reach at least check_fn */ + if (res == 0)/* it->fn == fn */ check_fn = fn_next_block(check_fn); /* if it->fn < check_fn, simply continue iterating, we want to reach at least check_fn */ @@ -196,6 +181,9 @@ static int pdch_ulc_add_node(struct pdch_ulc *ulc, struct pdch_ulc_node *item) struct rb_node **n = &(ulc->tree_root.rb_node); struct rb_node *parent = NULL; + LOGPDCH(ulc->pdch, DRLCMAC, LOGL_DEBUG, "Reserving FN %u for type %s\n", + item->fn, get_value_string(pdch_ul_node_names, item->type)); + while (*n) { struct pdch_ulc_node *it; int res; @@ -203,7 +191,7 @@ static int pdch_ulc_add_node(struct pdch_ulc *ulc, struct pdch_ulc_node *item) it = container_of(*n, struct pdch_ulc_node, node); parent = *n; - res = fn_cmp(item->fn, it->fn); + res = gsm0502_fncmp(item->fn, it->fn); if (res < 0) { /* item->fn "BEFORE" it->fn */ n = &((*n)->rb_left); } else if (res > 0) { /* item->fn "AFTER" it->fn */ @@ -279,7 +267,7 @@ void pdch_ulc_release_tbf(struct pdch_ulc *ulc, const struct gprs_rlcmac_tbf *tb item_tbf = item->tbf_poll.poll_tbf; break; case PDCH_ULC_NODE_TBF_USF: - item_tbf = (struct gprs_rlcmac_tbf *)item->tbf_usf.ul_tbf; + item_tbf = ul_tbf_as_tbf_const(item->tbf_usf.ul_tbf); break; default: OSMO_ASSERT(0); @@ -303,9 +291,9 @@ void pdch_ulc_expire_fn(struct pdch_ulc *ulc, uint32_t fn) int res; struct rb_node *first; - while((first = rb_first(&ulc->tree_root))) { + while ((first = rb_first(&ulc->tree_root))) { item = container_of(first, struct pdch_ulc_node, node); - res = fn_cmp(item->fn, fn); + res = gsm0502_fncmp(item->fn, fn); if (res > 0) /* item->fn AFTER fn */ break; if (res < 0) { /* item->fn BEFORE fn */ @@ -320,7 +308,7 @@ void pdch_ulc_expire_fn(struct pdch_ulc *ulc, uint32_t fn) case PDCH_ULC_NODE_TBF_USF: LOGPDCH(ulc->pdch, DRLCMAC, LOGL_INFO, "Timeout for registered USF (FN=%u): %s\n", - item->fn, tbf_name((struct gprs_rlcmac_tbf *)item->tbf_usf.ul_tbf)); + item->fn, tbf_name(ul_tbf_as_tbf_const(item->tbf_usf.ul_tbf))); tbf_usf_timeout(item->tbf_usf.ul_tbf); break; case PDCH_ULC_NODE_TBF_POLL: diff --git a/src/pdch_ul_controller.h b/src/pdch_ul_controller.h index c56945f1..a488e9b9 100644 --- a/src/pdch_ul_controller.h +++ b/src/pdch_ul_controller.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -40,7 +36,6 @@ enum rrbp_field { struct pdch_ulc { struct gprs_rlcmac_pdch *pdch; /* back pointer */ - uint32_t last_fn; /* last FN rx from TDMA clock */ struct rb_root tree_root; void *pool_ctx; /* talloc pool of struct pdch_ulc_node */ }; @@ -89,7 +84,7 @@ int pdch_ulc_reserve_sba(struct pdch_ulc *ulc, struct gprs_rlcmac_sba *sba); bool pdch_ulc_fn_is_free(struct pdch_ulc *ulc, uint32_t fn); int pdch_ulc_get_next_free_rrbp_fn(struct pdch_ulc *ulc, uint32_t fn, uint32_t *poll_fn, unsigned int *rrbp); -uint32_t pdch_ulc_get_next_free_fn(struct pdch_ulc *ulc, uint32_t start_fn); +uint32_t pdch_ulc_get_next_free_fn(const struct pdch_ulc *ulc, uint32_t start_fn); struct pdch_ulc_node *pdch_ulc_get_node(struct pdch_ulc *ulc, uint32_t fn); struct pdch_ulc_node *pdch_ulc_pop_node(struct pdch_ulc *ulc, uint32_t fn); diff --git a/src/rlc.cpp b/src/rlc.cpp index a2cc52c2..2166e4d9 100644 --- a/src/rlc.cpp +++ b/src/rlc.cpp @@ -10,10 +10,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "bts.h" @@ -45,275 +41,6 @@ uint8_t *prepare(struct gprs_rlc_data *rlc, size_t block_data_len) return rlc->block; } -void gprs_rlc_v_b::reset() -{ - for (size_t i = 0; i < ARRAY_SIZE(m_v_b); ++i) - mark_invalid(i); -} - -void gprs_rlc_dl_window::reset() -{ - m_v_s = 0; - m_v_a = 0; - m_v_b.reset(); -} - -int gprs_rlc_dl_window::resend_needed() const -{ - for (uint16_t bsn = v_a(); bsn != v_s(); bsn = mod_sns(bsn + 1)) { - if (m_v_b.is_nacked(bsn) || m_v_b.is_resend(bsn)) - return bsn; - } - - return -1; -} - -int gprs_rlc_dl_window::mark_for_resend() -{ - int resend = 0; - - for (uint16_t bsn = v_a(); bsn != v_s(); bsn = mod_sns(bsn + 1)) { - if (m_v_b.is_unacked(bsn)) { - /* mark to be re-send */ - m_v_b.mark_resend(bsn); - resend += 1; - } - } - - return resend; -} - -/* Update the receive block bitmap */ -uint16_t gprs_rlc_ul_window::update_egprs_rbb(uint8_t *rbb) -{ - uint16_t i; - uint16_t bsn; - uint16_t bitmask = 0x80; - int8_t pos = 0; - int8_t bit_pos = 0; - for (i = 0, bsn = (v_q()+1); ((bsn < (v_r())) && (i < ws())); i++, - bsn = this->mod_sns(bsn + 1)) { - if (m_v_n.is_received(bsn)) { - rbb[pos] = rbb[pos] | bitmask; - } else { - rbb[pos] = rbb[pos] & (~bitmask); - } - bitmask = bitmask >> 1; - bit_pos++; - bit_pos = bit_pos % 8; - if (bit_pos == 0) { - pos++; - bitmask = 0x80; - } - } - return i; -} - -int gprs_rlc_dl_window::count_unacked() -{ - uint16_t unacked = 0; - uint16_t bsn; - - for (bsn = v_a(); bsn != v_s(); bsn = mod_sns(bsn + 1)) { - if (!m_v_b.is_acked(bsn)) - unacked += 1; - } - - return unacked; -} - -static uint16_t bitnum_to_bsn(int bitnum, uint16_t ssn) -{ - return (ssn - 1 - bitnum); -} - -void gprs_rlc_dl_window::update(struct gprs_rlcmac_bts *bts, const struct bitvec *rbb, - uint16_t first_bsn, uint16_t *lost, - uint16_t *received) -{ - unsigned dist = distance(); - unsigned num_blocks = rbb->cur_bit > dist - ? dist : rbb->cur_bit; - unsigned bsn; - - /* first_bsn is in range V(A)..V(S) */ - - for (unsigned int bitpos = 0; bitpos < num_blocks; bitpos++) { - bool is_ack; - bsn = mod_sns(first_bsn + bitpos); - if (bsn == mod_sns(v_a() - 1)) - break; - - is_ack = bitvec_get_bit_pos(rbb, bitpos) == 1; - - if (is_ack) { - LOGP(DRLCMACDL, LOGL_DEBUG, "- got ack for BSN=%d\n", bsn); - if (!m_v_b.is_acked(bsn)) - *received += 1; - m_v_b.mark_acked(bsn); - } else { - LOGP(DRLCMACDL, LOGL_DEBUG, "- got NACK for BSN=%d\n", bsn); - m_v_b.mark_nacked(bsn); - bts_do_rate_ctr_inc(bts, CTR_RLC_NACKED); - *lost += 1; - } - } -} - -void gprs_rlc_dl_window::update(struct gprs_rlcmac_bts *bts, char *show_rbb, uint16_t ssn, - uint16_t *lost, uint16_t *received) -{ - /* SSN - 1 is in range V(A)..V(S)-1 */ - for (int bitpos = 0; bitpos < ws(); bitpos++) { - uint16_t bsn = mod_sns(bitnum_to_bsn(bitpos, ssn)); - - if (bsn == mod_sns(v_a() - 1)) - break; - - if (show_rbb[ws() - 1 - bitpos] == 'R') { - LOGP(DRLCMACDL, LOGL_DEBUG, "- got ack for BSN=%d\n", bsn); - if (!m_v_b.is_acked(bsn)) - *received += 1; - m_v_b.mark_acked(bsn); - } else { - LOGP(DRLCMACDL, LOGL_DEBUG, "- got NACK for BSN=%d\n", bsn); - m_v_b.mark_nacked(bsn); - bts_do_rate_ctr_inc(bts, CTR_RLC_NACKED); - *lost += 1; - } - } -} - -int gprs_rlc_dl_window::move_window() -{ - int i; - uint16_t bsn; - int moved = 0; - - for (i = 0, bsn = v_a(); bsn != v_s(); i++, bsn = mod_sns(bsn + 1)) { - if (m_v_b.is_acked(bsn)) { - m_v_b.mark_invalid(bsn); - moved += 1; - } else - break; - } - - return moved; -} - -void gprs_rlc_dl_window::show_state(char *show_v_b) -{ - int i; - uint16_t bsn; - - for (i = 0, bsn = v_a(); bsn != v_s(); i++, bsn = mod_sns(bsn + 1)) { - uint16_t index = bsn & mod_sns_half(); - switch(m_v_b.get_state(index)) { - case GPRS_RLC_DL_BSN_INVALID: - show_v_b[i] = 'I'; - break; - case GPRS_RLC_DL_BSN_ACKED: - show_v_b[i] = 'A'; - break; - case GPRS_RLC_DL_BSN_RESEND: - show_v_b[i] = 'X'; - break; - case GPRS_RLC_DL_BSN_NACKED: - show_v_b[i] = 'N'; - break; - default: - show_v_b[i] = '?'; - } - } - show_v_b[i] = '\0'; -} - -void gprs_rlc_v_n::reset() -{ - for (size_t i = 0; i < ARRAY_SIZE(m_v_n); ++i) - m_v_n[i] = GPRS_RLC_UL_BSN_INVALID; -} - -void gprs_rlc_window::set_sns(uint16_t sns) -{ - OSMO_ASSERT(sns >= RLC_GPRS_SNS); - OSMO_ASSERT(sns <= RLC_MAX_SNS); - /* check for 2^n */ - OSMO_ASSERT((sns & (-sns)) == sns); - m_sns = sns; -} - -void gprs_rlc_window::set_ws(uint16_t ws) -{ - LOGP(DRLCMAC, LOGL_INFO, "ws(%d)\n", - ws); - OSMO_ASSERT(ws >= RLC_GPRS_SNS/2); - OSMO_ASSERT(ws <= RLC_MAX_SNS/2); - m_ws = ws; -} - -/* Update the receive block bitmap */ -void gprs_rlc_ul_window::update_rbb(char *rbb) -{ - int i; - for (i=0; i < ws(); i++) { - if (m_v_n.is_received((ssn()-1-i) & mod_sns())) - rbb[ws()-1-i] = 'R'; - else - rbb[ws()-1-i] = 'I'; - } -} - -/* Raise V(R) to highest received sequence number not received. */ -void gprs_rlc_ul_window::raise_v_r(const uint16_t bsn) -{ - uint16_t offset_v_r; - offset_v_r = mod_sns(bsn + 1 - v_r()); - /* Positive offset, so raise. */ - if (offset_v_r < (sns() >> 1)) { - while (offset_v_r--) { - if (offset_v_r) /* all except the received block */ - m_v_n.mark_missing(v_r()); - raise_v_r_to(1); - } - LOGP(DRLCMACUL, LOGL_DEBUG, "- Raising V(R) to %d\n", v_r()); - } -} - -/* - * Raise V(Q) if possible. This is looped until there is a gap - * (non received block) or the window is empty. - */ -uint16_t gprs_rlc_ul_window::raise_v_q() -{ - uint16_t count = 0; - - while (v_q() != v_r()) { - if (!m_v_n.is_received(v_q())) - break; - LOGP(DRLCMACUL, LOGL_DEBUG, "- Taking block %d out, raising " - "V(Q) to %d\n", v_q(), mod_sns(v_q() + 1)); - raise_v_q(1); - count += 1; - } - - return count; -} - -void gprs_rlc_ul_window::receive_bsn(const uint16_t bsn) -{ - m_v_n.mark_received(bsn); - raise_v_r(bsn); -} - -bool gprs_rlc_ul_window::invalidate_bsn(const uint16_t bsn) -{ - bool was_valid = m_v_n.is_received(bsn); - m_v_n.mark_missing(bsn); - - return was_valid; -} - static void gprs_rlc_data_header_init(struct gprs_rlc_data_info *rlc, enum CodingScheme cs, bool with_padding, unsigned int header_bits, const unsigned int spb) @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once #ifdef __cplusplus @@ -33,34 +29,10 @@ extern "C" { #include <string.h> #define RLC_GPRS_SNS 128 /* GPRS, must be power of 2 */ -#define RLC_GPRS_WS 64 /* max window size */ -#define RLC_EGPRS_MIN_WS 64 /* min window size */ -#define RLC_EGPRS_MAX_WS 1024 /* min window size */ #define RLC_EGPRS_SNS 2048 /* EGPRS, must be power of 2 */ -#define RLC_EGPRS_MAX_BSN_DELTA 512 #define RLC_MAX_SNS RLC_EGPRS_SNS -#define RLC_MAX_WS RLC_EGPRS_MAX_WS #define RLC_MAX_LEN 74 /* MCS-9 data unit */ -struct gprs_rlcmac_bts; - -/* The state of a BSN in the send/receive window */ -enum gprs_rlc_ul_bsn_state { - GPRS_RLC_UL_BSN_INVALID, - GPRS_RLC_UL_BSN_RECEIVED, - GPRS_RLC_UL_BSN_MISSING, - GPRS_RLC_UL_BSN_MAX, -}; - -enum gprs_rlc_dl_bsn_state { - GPRS_RLC_DL_BSN_INVALID, - GPRS_RLC_DL_BSN_NACKED, - GPRS_RLC_DL_BSN_ACKED, - GPRS_RLC_DL_BSN_UNACKED, - GPRS_RLC_DL_BSN_RESEND, - GPRS_RLC_DL_BSN_MAX, -}; - /* * EGPRS resegment status information for UL * When only first split block is received bsn state @@ -243,138 +215,6 @@ struct gprs_rlc { gprs_rlc_data m_blocks[RLC_MAX_SNS/2]; }; -/** - * TODO: for GPRS/EDGE maybe make sns a template parameter - * so we create specialized versions... - */ -struct gprs_rlc_v_b { - /* Check for an individual frame */ - bool is_unacked(int bsn) const; - bool is_nacked(int bsn) const; - bool is_acked(int bsn) const; - bool is_resend(int bsn) const; - bool is_invalid(int bsn) const; - gprs_rlc_dl_bsn_state get_state(int bsn) const; - - /* Mark a RLC frame for something */ - void mark_unacked(int bsn); - void mark_nacked(int bsn); - void mark_acked(int bsn); - void mark_resend(int bsn); - void mark_invalid(int bsn); - - void reset(); - - -private: - bool is_state(int bsn, const gprs_rlc_dl_bsn_state state) const; - void mark(int bsn, const gprs_rlc_dl_bsn_state state); - - gprs_rlc_dl_bsn_state m_v_b[RLC_MAX_SNS/2]; /* acknowledge state array */ -}; - - -/** - * TODO: The UL/DL code could/should share a base class. - */ -class gprs_rlc_window { -public: - gprs_rlc_window(); - - const uint16_t mod_sns() const; - const uint16_t mod_sns(uint16_t bsn) const; - const uint16_t sns() const; - const uint16_t ws() const; - - void set_sns(uint16_t sns); - void set_ws(uint16_t ws); - -protected: - uint16_t m_sns; - uint16_t m_ws; -}; - -struct gprs_rlc_dl_window: public gprs_rlc_window { - void reset(); - - bool window_stalled() const; - bool window_empty() const; - - void increment_send(); - void raise(int moves); - - const uint16_t v_s() const; - const uint16_t v_s_mod(int offset) const; - const uint16_t v_a() const; - const uint16_t distance() const; - - /* Methods to manage reception */ - int resend_needed() const; - int mark_for_resend(); - void update(struct gprs_rlcmac_bts *bts, char *show_rbb, uint16_t ssn, - uint16_t *lost, uint16_t *received); - void update(struct gprs_rlcmac_bts *bts, const struct bitvec *rbb, - uint16_t first_bsn, uint16_t *lost, - uint16_t *received); - int move_window(); - void show_state(char *show_rbb); - int count_unacked(); - - uint16_t m_v_s; /* send state */ - uint16_t m_v_a; /* ack state */ - - gprs_rlc_v_b m_v_b; - - gprs_rlc_dl_window(); -}; - -struct gprs_rlc_v_n { - void reset(); - - void mark_received(int bsn); - void mark_missing(int bsn); - - bool is_received(int bsn) const; - - gprs_rlc_ul_bsn_state state(int bsn) const; -private: - bool is_state(int bsn, const gprs_rlc_ul_bsn_state state) const; - void mark(int bsn, const gprs_rlc_ul_bsn_state state); - gprs_rlc_ul_bsn_state m_v_n[RLC_MAX_SNS/2]; /* receive state array */ -}; - -struct gprs_rlc_ul_window: public gprs_rlc_window { - const uint16_t v_r() const; - const uint16_t v_q() const; - - const void set_v_r(int); - const void set_v_q(int); - void reset_state(); - - const uint16_t ssn() const; - - bool is_in_window(uint16_t bsn) const; - bool is_received(uint16_t bsn) const; - - void update_rbb(char *rbb); - uint16_t update_egprs_rbb(uint8_t *rbb); - void raise_v_r_to(int moves); - void raise_v_r(const uint16_t bsn); - uint16_t raise_v_q(); - - void raise_v_q(int); - - void receive_bsn(const uint16_t bsn); - bool invalidate_bsn(const uint16_t bsn); - - uint16_t m_v_r; /* receive state */ - uint16_t m_v_q; /* receive window state */ - - gprs_rlc_v_n m_v_n; - - gprs_rlc_ul_window(); -}; - extern "C" { /* TS 44.060 10.2.2 */ struct rlc_ul_header { @@ -390,7 +230,7 @@ struct rlc_ul_header { uint8_t e:1, bsn:7; #elif OSMO_IS_BIG_ENDIAN -/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ uint8_t pt:2, cv:4, si:1, r:1; uint8_t spare:1, pi:1, tfi:5, ti:1; uint8_t bsn:7, e:1; @@ -409,7 +249,7 @@ struct rlc_dl_header { uint8_t e:1, bsn:7; #elif OSMO_IS_BIG_ENDIAN -/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ uint8_t pt:2, rrbp:2, s_p:1, usf:3; uint8_t pr:2, tfi:5, fbi:1; uint8_t bsn:7, e:1; @@ -422,7 +262,7 @@ struct rlc_li_field { m:1, li:6; #elif OSMO_IS_BIG_ENDIAN -/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ uint8_t li:6, m:1, e:1; #endif } __attribute__ ((packed)); @@ -432,248 +272,12 @@ struct rlc_li_field_egprs { uint8_t e:1, li:7; #elif OSMO_IS_BIG_ENDIAN -/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ uint8_t li:7, e:1; #endif } __attribute__ ((packed)); } -inline bool gprs_rlc_v_b::is_state(int bsn, const gprs_rlc_dl_bsn_state type) const -{ - return m_v_b[bsn & mod_sns_half()] == type; -} - -inline void gprs_rlc_v_b::mark(int bsn, const gprs_rlc_dl_bsn_state type) -{ - m_v_b[bsn & mod_sns_half()] = type; -} - -inline bool gprs_rlc_v_b::is_nacked(int bsn) const -{ - return is_state(bsn, GPRS_RLC_DL_BSN_NACKED); -} - -inline bool gprs_rlc_v_b::is_acked(int bsn) const -{ - return is_state(bsn, GPRS_RLC_DL_BSN_ACKED); -} - -inline bool gprs_rlc_v_b::is_unacked(int bsn) const -{ - return is_state(bsn, GPRS_RLC_DL_BSN_UNACKED); -} - -inline bool gprs_rlc_v_b::is_resend(int bsn) const -{ - return is_state(bsn, GPRS_RLC_DL_BSN_RESEND); -} - -inline bool gprs_rlc_v_b::is_invalid(int bsn) const -{ - return is_state(bsn, GPRS_RLC_DL_BSN_INVALID); -} - -inline gprs_rlc_dl_bsn_state gprs_rlc_v_b::get_state(int bsn) const -{ - return m_v_b[bsn & mod_sns_half()]; -} - -inline void gprs_rlc_v_b::mark_resend(int bsn) -{ - return mark(bsn, GPRS_RLC_DL_BSN_RESEND); -} - -inline void gprs_rlc_v_b::mark_unacked(int bsn) -{ - return mark(bsn, GPRS_RLC_DL_BSN_UNACKED); -} - -inline void gprs_rlc_v_b::mark_acked(int bsn) -{ - return mark(bsn, GPRS_RLC_DL_BSN_ACKED); -} - -inline void gprs_rlc_v_b::mark_nacked(int bsn) -{ - return mark(bsn, GPRS_RLC_DL_BSN_NACKED); -} - -inline void gprs_rlc_v_b::mark_invalid(int bsn) -{ - return mark(bsn, GPRS_RLC_DL_BSN_INVALID); -} - -inline gprs_rlc_window::gprs_rlc_window() - : m_sns(RLC_GPRS_SNS) - , m_ws(RLC_GPRS_WS) -{ -} - -inline const uint16_t gprs_rlc_window::sns() const -{ - return m_sns; -} - -inline const uint16_t gprs_rlc_window::ws() const -{ - return m_ws; -} - -inline const uint16_t gprs_rlc_window::mod_sns() const -{ - return sns() - 1; -} - -inline const uint16_t gprs_rlc_window::mod_sns(uint16_t bsn) const -{ - return bsn & mod_sns(); -} - -inline gprs_rlc_dl_window::gprs_rlc_dl_window() - : m_v_s(0) - , m_v_a(0) -{ - reset(); -} - -inline const uint16_t gprs_rlc_dl_window::v_s() const -{ - return m_v_s; -} - -inline const uint16_t gprs_rlc_dl_window::v_s_mod(int offset) const -{ - return mod_sns(m_v_s + offset); -} - -inline const uint16_t gprs_rlc_dl_window::v_a() const -{ - return m_v_a; -} - -inline bool gprs_rlc_dl_window::window_stalled() const -{ - return (mod_sns(m_v_s - m_v_a)) == ws(); -} - -inline bool gprs_rlc_dl_window::window_empty() const -{ - return m_v_s == m_v_a; -} - -inline void gprs_rlc_dl_window::increment_send() -{ - m_v_s = (m_v_s + 1) & mod_sns(); -} - -inline void gprs_rlc_dl_window::raise(int moves) -{ - m_v_a = (m_v_a + moves) & mod_sns(); -} - -inline const uint16_t gprs_rlc_dl_window::distance() const -{ - return (m_v_s - m_v_a) & mod_sns(); -} - -inline gprs_rlc_ul_window::gprs_rlc_ul_window() - : m_v_r(0) - , m_v_q(0) -{ - m_v_n.reset(); -} - -inline bool gprs_rlc_ul_window::is_in_window(uint16_t bsn) const -{ - uint16_t offset_v_q; - - /* current block relative to lowest unreceived block */ - offset_v_q = (bsn - m_v_q) & mod_sns(); - /* If out of window (may happen if blocks below V(Q) are received - * again. */ - return offset_v_q < ws(); -} - -inline bool gprs_rlc_ul_window::is_received(uint16_t bsn) const -{ - uint16_t offset_v_r; - - /* Offset to the end of the received window */ - offset_v_r = (m_v_r - 1 - bsn) & mod_sns(); - return is_in_window(bsn) && m_v_n.is_received(bsn) && offset_v_r < ws(); -} - -inline void gprs_rlc_ul_window::reset_state() -{ - m_v_r = 0; - m_v_q = 0; -} - -inline const void gprs_rlc_ul_window::set_v_r(int v_r) -{ - m_v_r = v_r; -} - -inline const void gprs_rlc_ul_window::set_v_q(int v_q) -{ - m_v_q = v_q; -} - -inline const uint16_t gprs_rlc_ul_window::v_r() const -{ - return m_v_r; -} - -inline const uint16_t gprs_rlc_ul_window::v_q() const -{ - return m_v_q; -} - -inline const uint16_t gprs_rlc_ul_window::ssn() const -{ - return m_v_r; -} - -inline void gprs_rlc_ul_window::raise_v_r_to(int moves) -{ - m_v_r = mod_sns(m_v_r + moves); -} - -inline void gprs_rlc_ul_window::raise_v_q(int incr) -{ - m_v_q = mod_sns(m_v_q + incr); -} - -inline void gprs_rlc_v_n::mark_received(int bsn) -{ - return mark(bsn, GPRS_RLC_UL_BSN_RECEIVED); -} - -inline void gprs_rlc_v_n::mark_missing(int bsn) -{ - return mark(bsn, GPRS_RLC_UL_BSN_MISSING); -} - -inline bool gprs_rlc_v_n::is_received(int bsn) const -{ - return is_state(bsn, GPRS_RLC_UL_BSN_RECEIVED); -} - -inline bool gprs_rlc_v_n::is_state(int bsn, gprs_rlc_ul_bsn_state type) const -{ - return m_v_n[bsn & mod_sns_half()] == type; -} - -inline void gprs_rlc_v_n::mark(int bsn, gprs_rlc_ul_bsn_state type) -{ - m_v_n[bsn & mod_sns_half()] = type; -} - -inline gprs_rlc_ul_bsn_state gprs_rlc_v_n::state(int bsn) const -{ - return m_v_n[bsn & mod_sns_half()]; -} - inline void gprs_rlc::init() { memset(m_blocks, 0, sizeof(m_blocks)); diff --git a/src/rlc_window.cpp b/src/rlc_window.cpp new file mode 100644 index 00000000..c056170f --- /dev/null +++ b/src/rlc_window.cpp @@ -0,0 +1,42 @@ +/* RLC Window (common for both UL/DL TBF), 3GPP TS 44.060 + * + * Copyright (C) 2013 by Holger Hans Peter Freyther + * Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "gprs_debug.h" +#include "rlc_window.h" + +extern "C" { +#include <osmocom/core/utils.h> +#include <osmocom/core/bitvec.h> +#include <osmocom/core/logging.h> +} + +void gprs_rlc_window::set_sns(uint16_t sns) +{ + OSMO_ASSERT(sns >= RLC_GPRS_SNS); + OSMO_ASSERT(sns <= RLC_MAX_SNS); + /* check for 2^n */ + OSMO_ASSERT((sns & (-sns)) == sns); + m_sns = sns; +} + +void gprs_rlc_window::set_ws(uint16_t ws) +{ + LOGP(DRLCMAC, LOGL_INFO, "ws(%d)\n", + ws); + OSMO_ASSERT(ws >= RLC_GPRS_SNS/2); + OSMO_ASSERT(ws <= RLC_MAX_SNS/2); + m_ws = ws; +}
\ No newline at end of file diff --git a/src/rlc_window.h b/src/rlc_window.h new file mode 100644 index 00000000..1b0227f4 --- /dev/null +++ b/src/rlc_window.h @@ -0,0 +1,71 @@ +/* RLC Window (common for both UL/DL TBF), 3GPP TS 44.060 + * + * Copyright (C) 2012 Ivan Klyuchnikov + * Copyright (C) 2012 Andreas Eversberg <jolly@eversberg.eu> + * Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#pragma once + +#include <stdint.h> + +#include "rlc.h" + +#define RLC_GPRS_WS 64 /* max window size */ +#define RLC_EGPRS_MIN_WS 64 /* min window size */ +#define RLC_EGPRS_MAX_WS 1024 /* min window size */ +#define RLC_EGPRS_MAX_BSN_DELTA 512 +#define RLC_MAX_WS RLC_EGPRS_MAX_WS + +class gprs_rlc_window { +public: + gprs_rlc_window(); + + const uint16_t mod_sns(void) const; + const uint16_t mod_sns(uint16_t bsn) const; + const uint16_t sns(void) const; + const uint16_t ws(void) const; + + void set_sns(uint16_t sns); + void set_ws(uint16_t ws); + +protected: + uint16_t m_sns; + uint16_t m_ws; +}; + + +inline gprs_rlc_window::gprs_rlc_window(void) + : m_sns(RLC_GPRS_SNS) + , m_ws(RLC_GPRS_WS) +{ +} + +inline const uint16_t gprs_rlc_window::sns(void) const +{ + return m_sns; +} + +inline const uint16_t gprs_rlc_window::ws(void) const +{ + return m_ws; +} + +inline const uint16_t gprs_rlc_window::mod_sns(void) const +{ + return sns() - 1; +} + +inline const uint16_t gprs_rlc_window::mod_sns(uint16_t bsn) const +{ + return bsn & mod_sns(); +} diff --git a/src/rlc_window_dl.cpp b/src/rlc_window_dl.cpp new file mode 100644 index 00000000..41f9887d --- /dev/null +++ b/src/rlc_window_dl.cpp @@ -0,0 +1,182 @@ +/* RLC Window (DL TBF), 3GPP TS 44.060 + * + * Copyright (C) 2013 by Holger Hans Peter Freyther + * Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "gprs_debug.h" +#include "bts.h" +#include "rlc_window_dl.h" + +extern "C" { +#include <osmocom/core/utils.h> +#include <osmocom/core/bitvec.h> +#include <osmocom/core/logging.h> +} + +static uint16_t bitnum_to_bsn(int bitnum, uint16_t ssn) +{ + return (ssn - 1 - bitnum); +} + +void gprs_rlc_v_b::reset() +{ + for (size_t i = 0; i < ARRAY_SIZE(m_v_b); ++i) + mark_invalid(i); +} + +void gprs_rlc_dl_window::reset() +{ + m_v_s = 0; + m_v_a = 0; + m_v_b.reset(); +} + +int gprs_rlc_dl_window::resend_needed() const +{ + for (uint16_t bsn = v_a(); bsn != v_s(); bsn = mod_sns(bsn + 1)) { + if (m_v_b.is_nacked(bsn) || m_v_b.is_resend(bsn)) + return bsn; + } + + return -1; +} + +int gprs_rlc_dl_window::mark_for_resend() +{ + int resend = 0; + + for (uint16_t bsn = v_a(); bsn != v_s(); bsn = mod_sns(bsn + 1)) { + if (m_v_b.is_unacked(bsn)) { + /* mark to be re-send */ + m_v_b.mark_resend(bsn); + resend += 1; + } + } + + return resend; +} + +int gprs_rlc_dl_window::count_unacked() +{ + uint16_t unacked = 0; + uint16_t bsn; + + for (bsn = v_a(); bsn != v_s(); bsn = mod_sns(bsn + 1)) { + if (!m_v_b.is_acked(bsn)) + unacked += 1; + } + + return unacked; +} + +void gprs_rlc_dl_window::update(struct gprs_rlcmac_bts *bts, const struct bitvec *rbb, + uint16_t first_bsn, uint16_t *lost, + uint16_t *received) +{ + unsigned dist = distance(); + unsigned num_blocks = rbb->cur_bit > dist + ? dist : rbb->cur_bit; + unsigned bsn; + + /* first_bsn is in range V(A)..V(S) */ + + for (unsigned int bitpos = 0; bitpos < num_blocks; bitpos++) { + bool is_ack; + bsn = mod_sns(first_bsn + bitpos); + if (bsn == mod_sns(v_a() - 1)) + break; + + is_ack = bitvec_get_bit_pos(rbb, bitpos) == 1; + + if (is_ack) { + LOGP(DRLCMACDL, LOGL_DEBUG, "- got ack for BSN=%d\n", bsn); + if (!m_v_b.is_acked(bsn)) + *received += 1; + m_v_b.mark_acked(bsn); + } else { + LOGP(DRLCMACDL, LOGL_DEBUG, "- got NACK for BSN=%d\n", bsn); + m_v_b.mark_nacked(bsn); + bts_do_rate_ctr_inc(bts, CTR_RLC_NACKED); + *lost += 1; + } + } +} + +void gprs_rlc_dl_window::update(struct gprs_rlcmac_bts *bts, char *show_rbb, uint16_t ssn, + uint16_t *lost, uint16_t *received) +{ + /* SSN - 1 is in range V(A)..V(S)-1 */ + for (int bitpos = 0; bitpos < ws(); bitpos++) { + uint16_t bsn = mod_sns(bitnum_to_bsn(bitpos, ssn)); + + if (bsn == mod_sns(v_a() - 1)) + break; + + if (show_rbb[ws() - 1 - bitpos] == 'R') { + LOGP(DRLCMACDL, LOGL_DEBUG, "- got ack for BSN=%d\n", bsn); + if (!m_v_b.is_acked(bsn)) + *received += 1; + m_v_b.mark_acked(bsn); + } else { + LOGP(DRLCMACDL, LOGL_DEBUG, "- got NACK for BSN=%d\n", bsn); + m_v_b.mark_nacked(bsn); + bts_do_rate_ctr_inc(bts, CTR_RLC_NACKED); + *lost += 1; + } + } +} + +int gprs_rlc_dl_window::move_window() +{ + int i; + uint16_t bsn; + int moved = 0; + + for (i = 0, bsn = v_a(); bsn != v_s(); i++, bsn = mod_sns(bsn + 1)) { + if (m_v_b.is_acked(bsn)) { + m_v_b.mark_invalid(bsn); + moved += 1; + } else + break; + } + + return moved; +} + +void gprs_rlc_dl_window::show_state(char *show_v_b) +{ + int i; + uint16_t bsn; + + for (i = 0, bsn = v_a(); bsn != v_s(); i++, bsn = mod_sns(bsn + 1)) { + uint16_t index = bsn & mod_sns_half(); + switch(m_v_b.get_state(index)) { + case GPRS_RLC_DL_BSN_INVALID: + show_v_b[i] = 'I'; + break; + case GPRS_RLC_DL_BSN_ACKED: + show_v_b[i] = 'A'; + break; + case GPRS_RLC_DL_BSN_RESEND: + show_v_b[i] = 'X'; + break; + case GPRS_RLC_DL_BSN_NACKED: + show_v_b[i] = 'N'; + break; + default: + show_v_b[i] = '?'; + } + } + show_v_b[i] = '\0'; +}
\ No newline at end of file diff --git a/src/rlc_window_dl.h b/src/rlc_window_dl.h new file mode 100644 index 00000000..e2da6145 --- /dev/null +++ b/src/rlc_window_dl.h @@ -0,0 +1,205 @@ +/* RLC Window (DL TBF), 3GPP TS 44.060 + * + * Copyright (C) 2012 Ivan Klyuchnikov + * Copyright (C) 2012 Andreas Eversberg <jolly@eversberg.eu> + * Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#pragma once + +#include "rlc.h" +#include "rlc_window.h" + +enum gprs_rlc_dl_bsn_state { + GPRS_RLC_DL_BSN_INVALID, + GPRS_RLC_DL_BSN_NACKED, + GPRS_RLC_DL_BSN_ACKED, + GPRS_RLC_DL_BSN_UNACKED, + GPRS_RLC_DL_BSN_RESEND, + GPRS_RLC_DL_BSN_MAX, +}; + +/** + * TODO: for GPRS/EDGE maybe make sns a template parameter + * so we create specialized versions... + */ +struct gprs_rlc_v_b { + /* Check for an individual frame */ + bool is_unacked(int bsn) const; + bool is_nacked(int bsn) const; + bool is_acked(int bsn) const; + bool is_resend(int bsn) const; + bool is_invalid(int bsn) const; + gprs_rlc_dl_bsn_state get_state(int bsn) const; + + /* Mark a RLC frame for something */ + void mark_unacked(int bsn); + void mark_nacked(int bsn); + void mark_acked(int bsn); + void mark_resend(int bsn); + void mark_invalid(int bsn); + + void reset(void); + + +private: + bool is_state(int bsn, const gprs_rlc_dl_bsn_state state) const; + void mark(int bsn, const gprs_rlc_dl_bsn_state state); + + gprs_rlc_dl_bsn_state m_v_b[RLC_MAX_SNS/2]; /* acknowledge state array */ +}; + +inline bool gprs_rlc_v_b::is_state(int bsn, const gprs_rlc_dl_bsn_state type) const +{ + return m_v_b[bsn & mod_sns_half()] == type; +} + +inline void gprs_rlc_v_b::mark(int bsn, const gprs_rlc_dl_bsn_state type) +{ + m_v_b[bsn & mod_sns_half()] = type; +} + +inline bool gprs_rlc_v_b::is_nacked(int bsn) const +{ + return is_state(bsn, GPRS_RLC_DL_BSN_NACKED); +} + +inline bool gprs_rlc_v_b::is_acked(int bsn) const +{ + return is_state(bsn, GPRS_RLC_DL_BSN_ACKED); +} + +inline bool gprs_rlc_v_b::is_unacked(int bsn) const +{ + return is_state(bsn, GPRS_RLC_DL_BSN_UNACKED); +} + +inline bool gprs_rlc_v_b::is_resend(int bsn) const +{ + return is_state(bsn, GPRS_RLC_DL_BSN_RESEND); +} + +inline bool gprs_rlc_v_b::is_invalid(int bsn) const +{ + return is_state(bsn, GPRS_RLC_DL_BSN_INVALID); +} + +inline gprs_rlc_dl_bsn_state gprs_rlc_v_b::get_state(int bsn) const +{ + return m_v_b[bsn & mod_sns_half()]; +} + +inline void gprs_rlc_v_b::mark_resend(int bsn) +{ + return mark(bsn, GPRS_RLC_DL_BSN_RESEND); +} + +inline void gprs_rlc_v_b::mark_unacked(int bsn) +{ + return mark(bsn, GPRS_RLC_DL_BSN_UNACKED); +} + +inline void gprs_rlc_v_b::mark_acked(int bsn) +{ + return mark(bsn, GPRS_RLC_DL_BSN_ACKED); +} + +inline void gprs_rlc_v_b::mark_nacked(int bsn) +{ + return mark(bsn, GPRS_RLC_DL_BSN_NACKED); +} + +inline void gprs_rlc_v_b::mark_invalid(int bsn) +{ + return mark(bsn, GPRS_RLC_DL_BSN_INVALID); +} + +struct gprs_rlc_dl_window : public gprs_rlc_window { + void reset(void); + + bool window_stalled(void) const; + bool window_empty(void) const; + + void increment_send(void); + void raise(int moves); + + const uint16_t v_s(void) const; + const uint16_t v_s_mod(int offset) const; + const uint16_t v_a(void) const; + const uint16_t distance(void) const; + + /* Methods to manage reception */ + int resend_needed(void) const; + int mark_for_resend(void); + void update(struct gprs_rlcmac_bts *bts, char *show_rbb, uint16_t ssn, + uint16_t *lost, uint16_t *received); + void update(struct gprs_rlcmac_bts *bts, const struct bitvec *rbb, + uint16_t first_bsn, uint16_t *lost, + uint16_t *received); + int move_window(void); + void show_state(char *show_v_b); + int count_unacked(void); + + uint16_t m_v_s; /* send state */ + uint16_t m_v_a; /* ack state */ + + gprs_rlc_v_b m_v_b; + + gprs_rlc_dl_window(void); +}; + +inline gprs_rlc_dl_window::gprs_rlc_dl_window(void) + : m_v_s(0) + , m_v_a(0) +{ + reset(); +} + +inline const uint16_t gprs_rlc_dl_window::v_s(void) const +{ + return m_v_s; +} + +inline const uint16_t gprs_rlc_dl_window::v_s_mod(int offset) const +{ + return mod_sns(m_v_s + offset); +} + +inline const uint16_t gprs_rlc_dl_window::v_a(void) const +{ + return m_v_a; +} + +inline bool gprs_rlc_dl_window::window_stalled(void) const +{ + return (mod_sns(m_v_s - m_v_a)) == ws(); +} + +inline bool gprs_rlc_dl_window::window_empty(void) const +{ + return m_v_s == m_v_a; +} + +inline void gprs_rlc_dl_window::increment_send(void) +{ + m_v_s = (m_v_s + 1) & mod_sns(); +} + +inline void gprs_rlc_dl_window::raise(int moves) +{ + m_v_a = (m_v_a + moves) & mod_sns(); +} + +inline const uint16_t gprs_rlc_dl_window::distance(void) const +{ + return (m_v_s - m_v_a) & mod_sns(); +} diff --git a/src/rlc_window_ul.cpp b/src/rlc_window_ul.cpp new file mode 100644 index 00000000..32810d1b --- /dev/null +++ b/src/rlc_window_ul.cpp @@ -0,0 +1,125 @@ +/* RLC Window (UL TBF), 3GPP TS 44.060 + * + * Copyright (C) 2013 by Holger Hans Peter Freyther + * Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "gprs_debug.h" +#include "rlc_window_ul.h" + +extern "C" { +#include <osmocom/core/utils.h> +#include <osmocom/core/bitvec.h> +#include <osmocom/core/logging.h> +} + +void gprs_rlc_v_n::reset() +{ + for (size_t i = 0; i < ARRAY_SIZE(m_v_n); ++i) + m_v_n[i] = GPRS_RLC_UL_BSN_INVALID; +} + +/* Raise V(R) to highest received sequence number not received. */ +void gprs_rlc_ul_window::raise_v_r(const uint16_t bsn) +{ + uint16_t offset_v_r; + offset_v_r = mod_sns(bsn + 1 - v_r()); + /* Positive offset, so raise. */ + if (offset_v_r < (sns() >> 1)) { + while (offset_v_r--) { + const uint16_t _v_r = v_r(); + const uint16_t bsn_no_longer_in_ws = mod_sns(_v_r - ws()); + LOGP(DRLCMACUL, LOGL_DEBUG, "- Mark BSN %u as INVALID\n", bsn_no_longer_in_ws); + m_v_n.mark_invalid(bsn_no_longer_in_ws); + if (offset_v_r) {/* all except the received block */ + LOGP(DRLCMACUL, LOGL_DEBUG, "- Mark BSN %u as MISSING\n", _v_r); + m_v_n.mark_missing(_v_r); + } + raise_v_r_to(1); + } + LOGP(DRLCMACUL, LOGL_DEBUG, "- Raising V(R) to %d\n", v_r()); + } +} + +/* + * Raise V(Q) if possible. This is looped until there is a gap + * (non received block) or the window is empty. + */ +uint16_t gprs_rlc_ul_window::raise_v_q() +{ + uint16_t count = 0; + + while (v_q() != v_r()) { + if (!m_v_n.is_received(v_q())) + break; + LOGP(DRLCMACUL, LOGL_DEBUG, "- Taking block %d out, raising " + "V(Q) to %d\n", v_q(), mod_sns(v_q() + 1)); + raise_v_q(1); + count += 1; + } + + return count; +} + +void gprs_rlc_ul_window::receive_bsn(const uint16_t bsn) +{ + m_v_n.mark_received(bsn); + raise_v_r(bsn); +} + +bool gprs_rlc_ul_window::invalidate_bsn(const uint16_t bsn) +{ + bool was_valid = m_v_n.is_received(bsn); + m_v_n.mark_missing(bsn); + + return was_valid; +} + + +/* Update the receive block bitmap */ +uint16_t gprs_rlc_ul_window::update_egprs_rbb(uint8_t *rbb) +{ + uint16_t i; + uint16_t bsn; + uint16_t bitmask = 0x80; + int8_t pos = 0; + int8_t bit_pos = 0; + for (i = 0, bsn = (v_q()+1); ((bsn < (v_r())) && (i < ws())); i++, + bsn = this->mod_sns(bsn + 1)) { + if (m_v_n.is_received(bsn)) { + rbb[pos] = rbb[pos] | bitmask; + } else { + rbb[pos] = rbb[pos] & (~bitmask); + } + bitmask = bitmask >> 1; + bit_pos++; + bit_pos = bit_pos % 8; + if (bit_pos == 0) { + pos++; + bitmask = 0x80; + } + } + return i; +} + +/* Update the receive block bitmap */ +void gprs_rlc_ul_window::update_rbb(char *rbb) +{ + int i; + for (i=0; i < ws(); i++) { + if (m_v_n.is_received((ssn()-1-i) & mod_sns())) + rbb[ws()-1-i] = 'R'; + else + rbb[ws()-1-i] = 'I'; + } +}
\ No newline at end of file diff --git a/src/rlc_window_ul.h b/src/rlc_window_ul.h new file mode 100644 index 00000000..8a48af92 --- /dev/null +++ b/src/rlc_window_ul.h @@ -0,0 +1,180 @@ +/* RLC Window (UL TBF), 3GPP TS 44.060 + * + * Copyright (C) 2012 Ivan Klyuchnikov + * Copyright (C) 2012 Andreas Eversberg <jolly@eversberg.eu> + * Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#pragma once + +#include "rlc.h" +#include "rlc_window.h" + +/* The state of a BSN in the send/receive window */ +enum gprs_rlc_ul_bsn_state { + GPRS_RLC_UL_BSN_INVALID, + GPRS_RLC_UL_BSN_RECEIVED, + GPRS_RLC_UL_BSN_MISSING, + GPRS_RLC_UL_BSN_MAX, +}; + +struct gprs_rlc_v_n { + void reset(void); + + void mark_received(int bsn); + void mark_missing(int bsn); + void mark_invalid(int bsn); + + bool is_received(int bsn) const; + + gprs_rlc_ul_bsn_state state(int bsn) const; +private: + bool is_state(int bsn, const gprs_rlc_ul_bsn_state state) const; + void mark(int bsn, const gprs_rlc_ul_bsn_state state); + gprs_rlc_ul_bsn_state m_v_n[RLC_MAX_SNS/2]; /* receive state array */ +}; + + +inline void gprs_rlc_v_n::mark_received(int bsn) +{ + return mark(bsn, GPRS_RLC_UL_BSN_RECEIVED); +} + +inline void gprs_rlc_v_n::mark_missing(int bsn) +{ + return mark(bsn, GPRS_RLC_UL_BSN_MISSING); +} + +inline void gprs_rlc_v_n::mark_invalid(int bsn) +{ + return mark(bsn, GPRS_RLC_UL_BSN_INVALID); +} + +inline bool gprs_rlc_v_n::is_received(int bsn) const +{ + return is_state(bsn, GPRS_RLC_UL_BSN_RECEIVED); +} + +inline bool gprs_rlc_v_n::is_state(int bsn, gprs_rlc_ul_bsn_state type) const +{ + return m_v_n[bsn & mod_sns_half()] == type; +} + +inline void gprs_rlc_v_n::mark(int bsn, gprs_rlc_ul_bsn_state type) +{ + m_v_n[bsn & mod_sns_half()] = type; +} + +inline gprs_rlc_ul_bsn_state gprs_rlc_v_n::state(int bsn) const +{ + return m_v_n[bsn & mod_sns_half()]; +} + +struct gprs_rlc_ul_window : public gprs_rlc_window { + const uint16_t v_r(void) const; + const uint16_t v_q(void) const; + + const void set_v_r(int v_r); + const void set_v_q(int v_q); + void reset_state(void); + + const uint16_t ssn(void) const; + + bool is_in_window(uint16_t bsn) const; + bool is_received(uint16_t bsn) const; + + void update_rbb(char *rbb); + uint16_t update_egprs_rbb(uint8_t *rbb); + void raise_v_r_to(int moves); + void raise_v_r(const uint16_t bsn); + uint16_t raise_v_q(void); + + void raise_v_q(int incr); + + void receive_bsn(const uint16_t bsn); + bool invalidate_bsn(const uint16_t bsn); + + uint16_t m_v_r; /* receive state */ + uint16_t m_v_q; /* receive window state */ + + gprs_rlc_v_n m_v_n; + + gprs_rlc_ul_window(void); +}; + + +inline gprs_rlc_ul_window::gprs_rlc_ul_window(void) +{ + reset_state(); +} + +inline bool gprs_rlc_ul_window::is_in_window(uint16_t bsn) const +{ + uint16_t offset_v_q; + + /* current block relative to lowest unreceived block */ + offset_v_q = (bsn - m_v_q) & mod_sns(); + /* If out of window (may happen if blocks below V(Q) are received + * again. */ + return offset_v_q < ws(); +} + +inline bool gprs_rlc_ul_window::is_received(uint16_t bsn) const +{ + uint16_t offset_v_r; + + /* Offset to the end of the received window */ + offset_v_r = (m_v_r - 1 - bsn) & mod_sns(); + return is_in_window(bsn) && m_v_n.is_received(bsn) && offset_v_r < ws(); +} + +inline void gprs_rlc_ul_window::reset_state(void) +{ + m_v_r = 0; + m_v_q = 0; + m_v_n.reset(); +} + +inline const void gprs_rlc_ul_window::set_v_r(int v_r) +{ + m_v_r = v_r; +} + +inline const void gprs_rlc_ul_window::set_v_q(int v_q) +{ + m_v_q = v_q; +} + +inline const uint16_t gprs_rlc_ul_window::v_r(void) const +{ + return m_v_r; +} + +inline const uint16_t gprs_rlc_ul_window::v_q(void) const +{ + return m_v_q; +} + +inline const uint16_t gprs_rlc_ul_window::ssn(void) const +{ + return m_v_r; +} + +inline void gprs_rlc_ul_window::raise_v_r_to(int moves) +{ + m_v_r = mod_sns(m_v_r + moves); +} + +inline void gprs_rlc_ul_window::raise_v_q(int incr) +{ + m_v_q = mod_sns(m_v_q + incr); +} @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <sba.h> @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once diff --git a/src/tbf.cpp b/src/tbf.cpp index 07732dc4..840a1f71 100644 --- a/src/tbf.cpp +++ b/src/tbf.cpp @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <new> @@ -32,9 +28,9 @@ #include <gprs_debug.h> #include <gprs_ms.h> #include <pcu_utils.h> -#include <gprs_ms_storage.h> #include <sba.h> #include <pdch.h> +#include <alloc_algo.h> extern "C" { #include <osmocom/core/msgb.h> @@ -94,14 +90,12 @@ gprs_rlcmac_tbf::Meas::Meas() : gprs_rlcmac_tbf::gprs_rlcmac_tbf(struct gprs_rlcmac_bts *bts_, GprsMs *ms, gprs_rlcmac_tbf_direction dir) : direction(dir), trx(NULL), - first_ts(0), - first_common_ts(0), - control_ts(TBF_CONTROL_TS_UNSET), + control_ts(NULL), fT(0), num_fT_exp(0), upgrade_to_multislot(false), bts(bts_), - m_tfi(0), + m_tfi(TBF_TFI_UNSET), m_created_ts(0), m_ctrs(NULL), m_ms(ms), @@ -119,11 +113,6 @@ gprs_rlcmac_tbf::gprs_rlcmac_tbf(struct gprs_rlcmac_bts *bts_, GprsMs *ms, gprs_ memset(&m_trx_list, 0, sizeof(m_trx_list)); m_trx_list.entry = this; - memset(&state_fsm, 0, sizeof(state_fsm)); - state_fsm.tbf = this; - state_fsm.fi = osmo_fsm_inst_alloc(&tbf_fsm, this, &state_fsm, LOGL_INFO, NULL); - OSMO_ASSERT(state_fsm.fi); - memset(&ul_ass_fsm, 0, sizeof(ul_ass_fsm)); ul_ass_fsm.tbf = this; ul_ass_fsm.fi = osmo_fsm_inst_alloc(&tbf_ul_ass_fsm, this, &ul_ass_fsm, LOGL_INFO, NULL); @@ -134,16 +123,23 @@ gprs_rlcmac_tbf::gprs_rlcmac_tbf(struct gprs_rlcmac_bts *bts_, GprsMs *ms, gprs_ OSMO_ASSERT(dl_ass_fsm.fi); m_rlc.init(); - m_llc.init(); + llc_init(&m_llc); m_name_buf[0] = '\0'; + + m_created_ts = time(NULL); + /* set timestamp */ + osmo_clock_gettime(CLOCK_MONOTONIC, &meas.rssi_tv); + + m_ctrs = rate_ctr_group_alloc(this, &tbf_ctrg_desc, next_tbf_ctr_group_id++); + OSMO_ASSERT(m_ctrs); } gprs_rlcmac_tbf::~gprs_rlcmac_tbf() { - osmo_fsm_inst_free(state_fsm.fi); - state_fsm.fi = NULL; + osmo_fsm_inst_free(state_fi); + state_fi = NULL; osmo_fsm_inst_free(ul_ass_fsm.fi); ul_ass_fsm.fi = NULL; @@ -215,30 +211,7 @@ void gprs_rlcmac_tbf::set_ms(GprsMs *ms) ms_attach_tbf(m_ms, this); } -void gprs_rlcmac_tbf::update_ms(uint32_t tlli, enum gprs_rlcmac_tbf_direction dir) -{ - if (tlli == GSM_RESERVED_TMSI) - return; - - /* TODO: When the TLLI does not match the ms, check if there is another - * MS object that belongs to that TLLI and if yes make sure one of them - * gets deleted. This is the same problem that can arise with - * IMSI in gprs_rlcmac_dl_tbf::handle() so there should be a unified solution */ - if (!ms_check_tlli(ms(), tlli)) { - GprsMs *old_ms; - - old_ms = bts_ms_store(bts)->get_ms(tlli, 0, NULL); - if (old_ms) - ms_merge_and_clear_ms(ms(), old_ms); - } - - if (dir == GPRS_RLCMAC_UL_TBF) - ms_set_tlli(ms(), tlli); - else - ms_confirm_tlli(ms(), tlli); -} - -static void tbf_unlink_pdch(struct gprs_rlcmac_tbf *tbf) +void tbf_unlink_pdch(struct gprs_rlcmac_tbf *tbf) { int ts; @@ -249,8 +222,10 @@ static void tbf_unlink_pdch(struct gprs_rlcmac_tbf *tbf) * confirmation from the MS and goes through the FLOW state. Hence, we * may have ULC pollings ongoing and we need to make sure we drop all * reserved nodes there: */ - if (tbf->control_ts != TBF_CONTROL_TS_UNSET && !tbf->pdch[tbf->control_ts]) - pdch_ulc_release_tbf(tbf->trx->pdch[tbf->control_ts].ulc, tbf); + if (tbf->control_ts) { + pdch_ulc_release_tbf(tbf->control_ts->ulc, tbf); + tbf->control_ts = NULL; + } /* Now simply detach from all attached PDCHs */ for (ts = 0; ts < 8; ts++) { @@ -260,6 +235,12 @@ static void tbf_unlink_pdch(struct gprs_rlcmac_tbf *tbf) tbf->pdch[ts]->detach_tbf(tbf); tbf->pdch[ts] = NULL; } + + /* Detach from TRX: */ + if (tbf->trx) { + llist_del(tbf_trx_list(tbf)); + tbf->trx = NULL; + } } void tbf_free(struct gprs_rlcmac_tbf *tbf) @@ -270,7 +251,7 @@ void tbf_free(struct gprs_rlcmac_tbf *tbf) if (tbf->state_is(TBF_ST_FLOW)) bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_UL_ABORTED); } else { - gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(tbf); + gprs_rlcmac_dl_tbf *dl_tbf = tbf_as_dl_tbf(tbf); gprs_rlc_dl_window *win = static_cast<gprs_rlc_dl_window *>(dl_tbf->window()); bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_DL_FREED); @@ -291,7 +272,6 @@ void tbf_free(struct gprs_rlcmac_tbf *tbf) tbf->stop_timers("freeing TBF"); /* TODO: Could/Should generate bssgp_tx_llc_discarded */ tbf_unlink_pdch(tbf); - llist_del(tbf_trx_list(tbf)); if (tbf->ms()) tbf->set_ms(NULL); @@ -309,50 +289,38 @@ uint16_t egprs_window_size(const struct gprs_rlcmac_bts *bts, uint8_t slots) OSMO_MAX(64, (the_pcu->vty.ws_base + num_pdch * the_pcu->vty.ws_pdch) / 32 * 32)); } -int gprs_rlcmac_tbf::update() -{ - int rc; - - LOGP(DTBF, LOGL_DEBUG, "********** DL-TBF update **********\n"); - OSMO_ASSERT(direction == GPRS_RLCMAC_DL_TBF); - - tbf_unlink_pdch(this); - rc = the_pcu->alloc_algorithm(bts, this, false, -1); - /* if no resource */ - if (rc < 0) { - LOGPTBF(this, LOGL_ERROR, "No resource after update???\n"); - bts_do_rate_ctr_inc(bts, CTR_TBF_ALLOC_FAIL); - return rc; - } - - if (is_egprs_enabled()) { - gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(this); - if (dl_tbf) - dl_tbf->set_window_size(); - } - - tbf_update_state_fsm_name(this); - - return 0; -} - void tbf_assign_control_ts(struct gprs_rlcmac_tbf *tbf) { - if (tbf->control_ts == TBF_CONTROL_TS_UNSET) - LOGPTBF(tbf, LOGL_INFO, "Setting Control TS %d\n", - tbf->first_common_ts); - else if (tbf->control_ts != tbf->first_common_ts) - LOGPTBF(tbf, LOGL_INFO, "Changing Control TS %d -> %d\n", - tbf->control_ts, tbf->first_common_ts); - tbf->control_ts = tbf->first_common_ts; + char buf[128]; + struct gprs_rlcmac_pdch *first_common = ms_first_common_ts(tbf_ms(tbf)); + OSMO_ASSERT(first_common); + + if (!tbf->control_ts) + LOGPTBF(tbf, LOGL_INFO, "Setting Control TS %s\n", + pdch_name(first_common)); + else if (tbf->control_ts != first_common) + LOGPTBF(tbf, LOGL_INFO, "Changing Control TS %s -> %s\n", + pdch_name_buf(tbf->control_ts, buf, sizeof(buf)), + pdch_name(first_common)); + tbf->control_ts = first_common; } void gprs_rlcmac_tbf::n_reset(enum tbf_counters n) { - if (n >= N_MAX) { - LOGPTBF(this, LOGL_ERROR, "attempting to reset unknown counter %s\n", - get_value_string(tbf_counters_names, n)); - return; + OSMO_ASSERT(n < N_MAX); + + switch(n) { + case N3101: + OSMO_ASSERT(direction == GPRS_RLCMAC_UL_TBF); + break; + case N3103: + OSMO_ASSERT(direction == GPRS_RLCMAC_UL_TBF); + break; + case N3105: + OSMO_ASSERT(direction == GPRS_RLCMAC_DL_TBF); + break; + default: + break; } Narr[n] = 0; @@ -363,22 +331,21 @@ bool gprs_rlcmac_tbf::n_inc(enum tbf_counters n) { uint8_t chk; - if (n >= N_MAX) { - LOGPTBF(this, LOGL_ERROR, "attempting to increment unknown counter %s\n", - get_value_string(tbf_counters_names, n)); - return true; - } + OSMO_ASSERT(n < N_MAX); Narr[n]++; switch(n) { case N3101: + OSMO_ASSERT(direction == GPRS_RLCMAC_UL_TBF); chk = bts->n3101; break; case N3103: + OSMO_ASSERT(direction == GPRS_RLCMAC_UL_TBF); chk = bts->n3103; break; case N3105: + OSMO_ASSERT(direction == GPRS_RLCMAC_DL_TBF); chk = bts->n3105; break; default: @@ -491,8 +458,8 @@ void gprs_rlcmac_tbf::t_start(enum tbf_timers t, int T, const char *reason, bool OSMO_ASSERT(false); } - LOGPSRC(DTBF, LOGL_DEBUG, file, line, "%s %sstarting timer %s [%s] with %u sec. %u microsec, cur_fn=%d\n", - tbf_name(this), osmo_timer_pending(&Tarr[t]) ? "re" : "", + LOGPSRC(DTBF, LOGL_DEBUG, file, line, "%s %starting timer %s [%s] with %u sec. %u microsec, cur_fn=%d\n", + tbf_name(this), osmo_timer_pending(&Tarr[t]) ? "Res" : "S", get_value_string(tbf_timers_names, t), reason, sec, microsec, current_fn); Tarr[t].data = this; @@ -512,46 +479,18 @@ void gprs_rlcmac_tbf::t_start(enum tbf_timers t, int T, const char *reason, bool osmo_timer_schedule(&Tarr[t], sec, microsec); } -int gprs_rlcmac_tbf::check_polling(uint32_t fn, uint8_t ts, - uint32_t *poll_fn_, unsigned int *rrbp_) const -{ - int rc; - if (!is_control_ts(ts)) { - LOGPTBF(this, LOGL_DEBUG, "Polling cannot be " - "scheduled in this TS %d (first control TS %d)\n", - ts, control_ts); - return -EINVAL; - } - - if ((rc = pdch_ulc_get_next_free_rrbp_fn(trx->pdch[ts].ulc, fn, poll_fn_, rrbp_)) < 0) { - LOGPTBF(this, LOGL_DEBUG, - "(bts=%u,trx=%u,ts=%u) FN=%u No suitable free RRBP offset found!\n", - trx->bts->nr, trx->trx_no, ts, fn); - return rc; - } - - return 0; -} - -void gprs_rlcmac_tbf::set_polling(uint32_t new_poll_fn, uint8_t ts, enum pdch_ulc_tbf_poll_reason reason) -{ - /* schedule polling */ - if (pdch_ulc_reserve_tbf_poll(trx->pdch[ts].ulc, new_poll_fn, this, reason) < 0) - LOGPTBFDL(this, LOGL_ERROR, "Failed scheduling poll on PACCH (FN=%d, TS=%d)\n", - new_poll_fn, ts); -} - void gprs_rlcmac_tbf::poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_fn, enum pdch_ulc_tbf_poll_reason reason) { gprs_rlcmac_ul_tbf *ul_tbf; gprs_rlcmac_dl_tbf *dl_tbf; - LOGPTBF(this, LOGL_NOTICE, "poll timeout for FN=%d, TS=%d (curr FN %d)\n", - poll_fn, pdch->ts_no, bts_current_frame_number(bts)); + LOGPTBF(this, LOGL_NOTICE, "poll timeout for FN=%d, TS=%d (curr FN %d), reason=%s\n", + poll_fn, pdch->ts_no, bts_current_frame_number(bts), + get_value_string(pdch_ulc_tbf_poll_reason_names, reason)); switch (reason) { case PDCH_ULC_POLL_UL_ACK: - ul_tbf = as_ul_tbf(this); + ul_tbf = tbf_as_ul_tbf(this); OSMO_ASSERT(ul_tbf); if (!tbf_ul_ack_exp_ctrl_ack(ul_tbf, poll_fn, pdch->ts_no)) { LOGPTBF(this, LOGL_NOTICE, "FN=%d, TS=%d (curr FN %d): POLL_UL_ACK not expected!\n", @@ -563,7 +502,7 @@ void gprs_rlcmac_tbf::poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_ if (state_is(TBF_ST_FINISHED)) { if (ul_tbf->n_inc(N3103)) { bts_do_rate_ctr_inc(bts, CTR_PUAN_POLL_FAILED); - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_MAX_N3103, NULL); + osmo_fsm_inst_dispatch(this->state_fi, TBF_EV_MAX_N3103, NULL); return; } } @@ -579,8 +518,8 @@ void gprs_rlcmac_tbf::poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_ } bts_do_rate_ctr_inc(bts, CTR_RLC_ASS_TIMEDOUT); bts_do_rate_ctr_inc(bts, CTR_PUA_POLL_TIMEDOUT); - if (n_inc(N3105)) { - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_MAX_N3105, NULL); + if (this->direction == GPRS_RLCMAC_DL_TBF && n_inc(N3105)) { + osmo_fsm_inst_dispatch(this->state_fi, TBF_EV_MAX_N3105, NULL); bts_do_rate_ctr_inc(bts, CTR_RLC_ASS_FAILED); bts_do_rate_ctr_inc(bts, CTR_PUA_POLL_FAILED); return; @@ -598,8 +537,8 @@ void gprs_rlcmac_tbf::poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_ } bts_do_rate_ctr_inc(bts, CTR_RLC_ASS_TIMEDOUT); bts_do_rate_ctr_inc(bts, CTR_PDA_POLL_TIMEDOUT); - if (n_inc(N3105)) { - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_MAX_N3105, NULL); + if (this->direction == GPRS_RLCMAC_DL_TBF && n_inc(N3105)) { + osmo_fsm_inst_dispatch(this->state_fi, TBF_EV_MAX_N3105, NULL); bts_do_rate_ctr_inc(bts, CTR_RLC_ASS_FAILED); bts_do_rate_ctr_inc(bts, CTR_PDA_POLL_FAILED); return; @@ -619,14 +558,14 @@ void gprs_rlcmac_tbf::poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_ break; case PDCH_ULC_POLL_DL_ACK: - dl_tbf = as_dl_tbf(this); + dl_tbf = tbf_as_dl_tbf(this); /* POLL Timeout expecting DL ACK/NACK: implies direction == GPRS_RLCMAC_DL_TBF */ OSMO_ASSERT(dl_tbf); - if (!(dl_tbf->state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_TO_DL_ACK))) { + if (!dl_tbf->m_last_dl_poll_ack_lost) { LOGPTBF(this, LOGL_NOTICE, "Timeout for polling PACKET DOWNLINK ACK: %s\n", tbf_rlcmac_diag(dl_tbf)); - dl_tbf->state_fsm.state_flags |= (1 << GPRS_RLCMAC_FLAG_TO_DL_ACK); + dl_tbf->m_last_dl_poll_ack_lost = true; } if (dl_tbf->state_is(TBF_ST_RELEASING)) bts_do_rate_ctr_inc(bts, CTR_RLC_REL_TIMEDOUT); @@ -635,13 +574,13 @@ void gprs_rlcmac_tbf::poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_ bts_do_rate_ctr_inc(bts, CTR_PDAN_POLL_TIMEDOUT); } if (dl_tbf->n_inc(N3105)) { - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_MAX_N3105, NULL); + osmo_fsm_inst_dispatch(this->state_fi, TBF_EV_MAX_N3105, NULL); bts_do_rate_ctr_inc(bts, CTR_PDAN_POLL_FAILED); bts_do_rate_ctr_inc(bts, CTR_RLC_ACK_FAILED); return; } /* resend IMM.ASS on CCCH on timeout */ - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_DL_ACKNACK_MISS, NULL); + osmo_fsm_inst_dispatch(this->state_fi, TBF_EV_DL_ACKNACK_MISS, NULL); break; default: @@ -650,99 +589,46 @@ void gprs_rlcmac_tbf::poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_ } } -int gprs_rlcmac_tbf::setup(int8_t use_trx, bool single_slot) -{ - int rc; - - if (ms_mode(m_ms) != GPRS) - enable_egprs(); - - m_created_ts = time(NULL); - /* select algorithm */ - rc = the_pcu->alloc_algorithm(bts, this, single_slot, use_trx); - /* if no resource */ - if (rc < 0) { - LOGPTBF(this, LOGL_NOTICE, - "Timeslot Allocation failed: trx = %d, single_slot = %d\n", - use_trx, single_slot); - bts_do_rate_ctr_inc(bts, CTR_TBF_ALLOC_FAIL); - return -1; - } - /* assign initial control ts */ - tbf_assign_control_ts(this); - - /* set timestamp */ - osmo_clock_gettime(CLOCK_MONOTONIC, &meas.rssi_tv); - - LOGPTBF(this, LOGL_INFO, - "Allocated: trx = %d, ul_slots = %02x, dl_slots = %02x\n", - this->trx->trx_no, ul_slots(), dl_slots()); - - m_ctrs = rate_ctr_group_alloc(this, &tbf_ctrg_desc, next_tbf_ctr_group_id++); - if (!m_ctrs) { - LOGPTBF(this, LOGL_ERROR, "Couldn't allocate TBF counters\n"); - return -1; - } - - tbf_update_state_fsm_name(this); - - ms_attach_tbf(m_ms, this); - - return 0; -} - -int gprs_rlcmac_tbf::establish_dl_tbf_on_pacch() -{ - struct gprs_rlcmac_dl_tbf *new_tbf = NULL; - - bts_do_rate_ctr_inc(bts, CTR_TBF_REUSED); - - new_tbf = tbf_alloc_dl_tbf(bts, ms(), - this->trx->trx_no, false); - - if (!new_tbf) { - LOGP(DTBF, LOGL_NOTICE, "No PDCH resource\n"); - return -1; - } - - LOGPTBF(this, LOGL_DEBUG, "Trigger downlink assignment on PACCH\n"); - new_tbf->trigger_ass(this); - - return 0; -} - const char *tbf_name(const gprs_rlcmac_tbf *tbf) { return tbf ? tbf->name() : "(no TBF)"; } -const char *gprs_rlcmac_tbf::name() const +const char *gprs_rlcmac_tbf::name(bool enclousure) const { - snprintf(m_name_buf, sizeof(m_name_buf) - 1, - "TBF(TFI=%d TLLI=0x%08x DIR=%s STATE=%s%s)", - m_tfi, tlli(), - direction == GPRS_RLCMAC_UL_TBF ? "UL" : "DL", - state_name(), - is_egprs_enabled() ? " EGPRS" : "" - ); - m_name_buf[sizeof(m_name_buf) - 1] = '\0'; + struct osmo_strbuf sb = { .buf = m_name_buf, .len = sizeof(m_name_buf) }; + + if (enclousure) + OSMO_STRBUF_PRINTF(sb, "TBF("); + OSMO_STRBUF_PRINTF(sb, "%s", direction == GPRS_RLCMAC_UL_TBF ? "UL" : "DL"); + if (this->trx) { /* This may not be available during TBF alloc func time */ + int8_t tfi = (m_tfi == TBF_TFI_UNSET) ? -1 : m_tfi; + OSMO_STRBUF_PRINTF(sb, ":TFI-%u-%u-%d", + this->trx->bts->nr, this->trx->trx_no, tfi); + } + OSMO_STRBUF_PRINTF(sb, ":%c", is_egprs_enabled() ? 'E' : 'G'); + if (m_ms) { + uint32_t tlli = ms_tlli(m_ms); + if (ms_imsi_is_valid(m_ms)) + OSMO_STRBUF_PRINTF(sb, ":IMSI-%s", ms_imsi(m_ms)); + if (tlli != GSM_RESERVED_TMSI) + OSMO_STRBUF_PRINTF(sb, ":TLLI-0x%08x", tlli); + } + if (enclousure) + OSMO_STRBUF_PRINTF(sb, "){%s}", state_name()); return m_name_buf; } void tbf_update_state_fsm_name(struct gprs_rlcmac_tbf *tbf) { - char buf[64]; - snprintf(buf, sizeof(buf), "%s-TFI_%d", - tbf_direction(tbf) == GPRS_RLCMAC_UL_TBF ? "UL" : "DL", - tbf_tfi(tbf)); - osmo_identifier_sanitize_buf(buf, NULL, '_'); + const char *buf = tbf->name(false); - osmo_fsm_inst_update_id(tbf->state_fsm.fi, buf); + osmo_fsm_inst_update_id(tbf->state_fi, buf); osmo_fsm_inst_update_id(tbf->ul_ass_fsm.fi, buf); osmo_fsm_inst_update_id(tbf->dl_ass_fsm.fi, buf); if (tbf_direction(tbf) == GPRS_RLCMAC_UL_TBF) { - struct gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(tbf); + struct gprs_rlcmac_ul_tbf *ul_tbf = tbf_as_ul_tbf(tbf); osmo_fsm_inst_update_id(ul_tbf->ul_ack_fsm.fi, buf); } @@ -776,12 +662,14 @@ uint8_t gprs_rlcmac_tbf::ul_slots() const { uint8_t slots = 0; size_t i; + struct gprs_rlcmac_pdch *first_common; if (direction == GPRS_RLCMAC_DL_TBF) { - if (control_ts < 8) - slots |= 1 << control_ts; - if (first_common_ts < 8) - slots |= 1 << first_common_ts; + if (control_ts) + slots |= 1 << control_ts->ts_no; + first_common = ms_first_common_ts(tbf_ms(this)); + if (first_common) + slots |= 1 << first_common->ts_no; return slots; } @@ -793,11 +681,6 @@ uint8_t gprs_rlcmac_tbf::ul_slots() const return slots; } -bool gprs_rlcmac_tbf::is_control_ts(uint8_t ts) const -{ - return ts == control_ts; -} - void gprs_rlcmac_tbf::enable_egprs() { /* Decrease GPRS TBF count of attached PDCHs */ @@ -819,7 +702,12 @@ void gprs_rlcmac_tbf::enable_egprs() /* C API */ enum tbf_fsm_states tbf_state(const struct gprs_rlcmac_tbf *tbf) { - return (enum tbf_fsm_states)tbf->state_fsm.fi->state; + return (enum tbf_fsm_states)tbf->state_fi->state; +} + +struct osmo_fsm_inst *tbf_state_fi(const struct gprs_rlcmac_tbf *tbf) +{ + return tbf->state_fi; } struct osmo_fsm_inst *tbf_ul_ass_fi(const struct gprs_rlcmac_tbf *tbf) @@ -867,11 +755,6 @@ struct gprs_llc *tbf_llc(struct gprs_rlcmac_tbf *tbf) return &tbf->m_llc; } -uint8_t tbf_first_common_ts(const struct gprs_rlcmac_tbf *tbf) -{ - return tbf->first_common_ts; -} - uint8_t tbf_dl_slots(const struct gprs_rlcmac_tbf *tbf) { return tbf->dl_slots(); @@ -883,7 +766,19 @@ uint8_t tbf_ul_slots(const struct gprs_rlcmac_tbf *tbf) bool tbf_is_tfi_assigned(const struct gprs_rlcmac_tbf *tbf) { - return tbf->is_tfi_assigned(); + const struct gprs_rlcmac_dl_tbf *dl_tbf; + + /* The TBF is established: */ + if (tbf->state_fi->state > TBF_ST_ASSIGN) + return true; + + /* The DL TBF has been assigned by a IMM.ASS: */ + dl_tbf = tbf_as_dl_tbf_const(tbf); + if (tbf->state_fi->state == TBF_ST_ASSIGN && dl_tbf && + (dl_tbf->state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH))) + return true; + + return false; } uint8_t tbf_tfi(const struct gprs_rlcmac_tbf *tbf) @@ -896,14 +791,37 @@ bool tbf_is_egprs_enabled(const struct gprs_rlcmac_tbf *tbf) return tbf->is_egprs_enabled(); } -int tbf_check_polling(const struct gprs_rlcmac_tbf *tbf, uint32_t fn, uint8_t ts, uint32_t *poll_fn, unsigned int *rrbp) +int tbf_check_polling(const struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch, + uint32_t fn, uint32_t *poll_fn, unsigned int *rrbp) { - return tbf->check_polling(fn, ts, poll_fn, rrbp); + int rc; + OSMO_ASSERT(pdch); + + if (!tbf_is_control_ts(tbf, pdch)) { + char buf[128]; + LOGPTBF(tbf, LOGL_NOTICE, "Polling cannot be " + "scheduled in this TS %s (control TS %s)\n", + pdch_name(pdch), + tbf->control_ts ? pdch_name_buf(tbf->control_ts, buf, sizeof(buf)) : "none"); + return -EINVAL; + } + + if ((rc = pdch_ulc_get_next_free_rrbp_fn(pdch->ulc, fn, poll_fn, rrbp)) < 0) { + LOGPTBF(tbf, LOGL_NOTICE, + "FN=%u No suitable free RRBP offset found on %s!\n", + fn, pdch_name(pdch)); + return rc; + } + + return 0; } -void tbf_set_polling(struct gprs_rlcmac_tbf *tbf, uint32_t new_poll_fn, uint8_t ts, enum pdch_ulc_tbf_poll_reason t) +void tbf_set_polling(struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch, uint32_t new_poll_fn, enum pdch_ulc_tbf_poll_reason t) { - return tbf->set_polling(new_poll_fn, ts, t); + /* schedule polling */ + if (pdch_ulc_reserve_tbf_poll(pdch->ulc, new_poll_fn, tbf, t) < 0) + LOGPTBF(tbf, LOGL_ERROR, "FN=%u Failed scheduling poll on PACCH %s\n", + new_poll_fn, pdch_name(pdch)); } void tbf_poll_timeout(struct gprs_rlcmac_tbf *tbf, struct gprs_rlcmac_pdch *pdch, uint32_t poll_fn, enum pdch_ulc_tbf_poll_reason reason) @@ -911,19 +829,39 @@ void tbf_poll_timeout(struct gprs_rlcmac_tbf *tbf, struct gprs_rlcmac_pdch *pdch tbf->poll_timeout(pdch, poll_fn, reason); } -bool tbf_is_control_ts(const struct gprs_rlcmac_tbf *tbf, uint8_t ts) +bool tbf_is_control_ts(const struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch) { - return tbf->is_control_ts(ts); + return tbf->control_ts == pdch; } bool tbf_can_upgrade_to_multislot(const struct gprs_rlcmac_tbf *tbf) { return tbf->upgrade_to_multislot; } +/* first TS used by TBF */ +struct gprs_rlcmac_pdch *tbf_get_first_ts(struct gprs_rlcmac_tbf *tbf) +{ + unsigned int i; -int tbf_update(struct gprs_rlcmac_tbf *tbf) + for (i = 0; i < ARRAY_SIZE(tbf->pdch); i++) { + struct gprs_rlcmac_pdch *pdch; + pdch = tbf->pdch[i]; + if (pdch) + return pdch; + } + return NULL; +} +const struct gprs_rlcmac_pdch *tbf_get_first_ts_const(const struct gprs_rlcmac_tbf *tbf) { - return tbf->update(); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tbf->pdch); i++) { + const struct gprs_rlcmac_pdch *pdch; + pdch = tbf->pdch[i]; + if (pdch) + return pdch; + } + return NULL; } const char* tbf_rlcmac_diag(const struct gprs_rlcmac_tbf *tbf) @@ -932,18 +870,23 @@ const char* tbf_rlcmac_diag(const struct gprs_rlcmac_tbf *tbf) struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) }; OSMO_STRBUF_PRINTF(sb, "|"); - if (tbf->state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)) - OSMO_STRBUF_PRINTF(sb, "Assignment was on CCCH|"); - if (tbf->state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH)) - OSMO_STRBUF_PRINTF(sb, "Assignment was on PACCH|"); if (tbf->direction == GPRS_RLCMAC_UL_TBF) { - const struct gprs_rlcmac_ul_tbf *ul_tbf = static_cast<const gprs_rlcmac_ul_tbf *>(tbf); + const struct gprs_rlcmac_ul_tbf *ul_tbf = tbf_as_ul_tbf_const(tbf); + if (ul_tbf->state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)) + OSMO_STRBUF_PRINTF(sb, "Assignment was on CCCH|"); + if (ul_tbf->state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH)) + OSMO_STRBUF_PRINTF(sb, "Assignment was on PACCH|"); if (ul_tbf->m_rx_counter) OSMO_STRBUF_PRINTF(sb, "Uplink data was received|"); else OSMO_STRBUF_PRINTF(sb, "No uplink data received yet|"); } else { - if (tbf->state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_DL_ACK)) + const struct gprs_rlcmac_dl_tbf *dl_tbf = tbf_as_dl_tbf_const(tbf); + if (dl_tbf->state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)) + OSMO_STRBUF_PRINTF(sb, "Assignment was on CCCH|"); + if (dl_tbf->state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH)) + OSMO_STRBUF_PRINTF(sb, "Assignment was on PACCH|"); + if (dl_tbf->m_first_dl_ack_rcvd) OSMO_STRBUF_PRINTF(sb, "Downlink ACK was received|"); else OSMO_STRBUF_PRINTF(sb, "No downlink ACK received yet|"); @@ -951,3 +894,13 @@ const char* tbf_rlcmac_diag(const struct gprs_rlcmac_tbf *tbf) return buf; } + +struct gprs_rlcmac_trx *tbf_get_trx(struct gprs_rlcmac_tbf *tbf) +{ + return tbf->trx; +} + +void tbf_stop_timers(struct gprs_rlcmac_tbf *tbf, const char *reason) +{ + tbf->stop_timers(reason); +}
\ No newline at end of file @@ -10,10 +10,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -24,6 +20,7 @@ #include "llc.h" #include "rlc.h" +#include "rlc_window.h" #include "cxx_linuxlist.h" #include "pcu_utils.h" #include <gprs_debug.h> @@ -31,6 +28,7 @@ struct bssgp_bvc_ctx; struct gprs_rlcmac_bts; +struct alloc_resources_req; #endif @@ -112,11 +110,7 @@ enum tbf_counters { /* TBF counters from 3GPP TS 44.060 §13.4 */ #define GPRS_RLCMAC_FLAG_CCCH 0 /* assignment on CCCH */ #define GPRS_RLCMAC_FLAG_PACCH 1 /* assignment on PACCH */ -#define GPRS_RLCMAC_FLAG_DL_ACK 2 /* DL TBF: At least one DL ACK/NACK was recieved since it was assigned */ -#define GPRS_RLCMAC_FLAG_TO_DL_ACK 3 /* DL TBF: Failed to receive last polled DL ACK/NACK */ -#define GPRS_RLCMAC_FLAG_TO_MASK 0xf0 /* timeout bits */ - -#define TBF_CONTROL_TS_UNSET 0xff +#define TBF_TFI_UNSET 0xff #define T_START(tbf, t, T, r, f) tbf->t_start(t, T, r, f, __FILE__, __LINE__) @@ -128,6 +122,7 @@ const char *tbf_name(const struct gprs_rlcmac_tbf *tbf); enum tbf_fsm_states tbf_state(const struct gprs_rlcmac_tbf *tbf); struct osmo_fsm_inst *tbf_ul_ass_fi(const struct gprs_rlcmac_tbf *tbf); struct osmo_fsm_inst *tbf_dl_ass_fi(const struct gprs_rlcmac_tbf *tbf); +struct osmo_fsm_inst *tbf_state_fi(const struct gprs_rlcmac_tbf *tbf); enum gprs_rlcmac_tbf_direction tbf_direction(const struct gprs_rlcmac_tbf *tbf); void tbf_set_ms(struct gprs_rlcmac_tbf *tbf, struct GprsMs *ms); struct llist_head *tbf_ms_list(struct gprs_rlcmac_tbf *tbf); @@ -136,21 +131,24 @@ struct GprsMs *tbf_ms(const struct gprs_rlcmac_tbf *tbf); bool tbf_timers_pending(struct gprs_rlcmac_tbf *tbf, enum tbf_timers t); void tbf_free(struct gprs_rlcmac_tbf *tbf); struct gprs_llc *tbf_llc(struct gprs_rlcmac_tbf *tbf); -uint8_t tbf_first_common_ts(const struct gprs_rlcmac_tbf *tbf); uint8_t tbf_dl_slots(const struct gprs_rlcmac_tbf *tbf); uint8_t tbf_ul_slots(const struct gprs_rlcmac_tbf *tbf); bool tbf_is_tfi_assigned(const struct gprs_rlcmac_tbf *tbf); uint8_t tbf_tfi(const struct gprs_rlcmac_tbf *tbf); bool tbf_is_egprs_enabled(const struct gprs_rlcmac_tbf *tbf); void tbf_assign_control_ts(struct gprs_rlcmac_tbf *tbf); -int tbf_check_polling(const struct gprs_rlcmac_tbf *tbf, uint32_t fn, uint8_t ts, uint32_t *poll_fn, unsigned int *rrbp); -void tbf_set_polling(struct gprs_rlcmac_tbf *tbf, uint32_t new_poll_fn, uint8_t ts, enum pdch_ulc_tbf_poll_reason t); +int tbf_check_polling(const struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch, uint32_t fn, uint32_t *poll_fn, unsigned int *rrbp); +void tbf_set_polling(struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch, uint32_t new_poll_fn, enum pdch_ulc_tbf_poll_reason t); void tbf_poll_timeout(struct gprs_rlcmac_tbf *tbf, struct gprs_rlcmac_pdch *pdch, uint32_t poll_fn, enum pdch_ulc_tbf_poll_reason reason); void tbf_update_state_fsm_name(struct gprs_rlcmac_tbf *tbf); const char* tbf_rlcmac_diag(const struct gprs_rlcmac_tbf *tbf); -bool tbf_is_control_ts(const struct gprs_rlcmac_tbf *tbf, uint8_t ts); +bool tbf_is_control_ts(const struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch); bool tbf_can_upgrade_to_multislot(const struct gprs_rlcmac_tbf *tbf); -int tbf_update(struct gprs_rlcmac_tbf *tbf); +struct gprs_rlcmac_pdch *tbf_get_first_ts(struct gprs_rlcmac_tbf *tbf); +const struct gprs_rlcmac_pdch *tbf_get_first_ts_const(const struct gprs_rlcmac_tbf *tbf); +struct gprs_rlcmac_trx *tbf_get_trx(struct gprs_rlcmac_tbf *tbf); +void tbf_unlink_pdch(struct gprs_rlcmac_tbf *tbf); +void tbf_stop_timers(struct gprs_rlcmac_tbf *tbf, const char *reason); #ifdef __cplusplus } #endif @@ -162,18 +160,17 @@ struct gprs_rlcmac_tbf { virtual ~gprs_rlcmac_tbf(); virtual gprs_rlc_window *window() = 0; + virtual void apply_allocated_resources(const struct alloc_resources_res *res) = 0; - int setup(int8_t use_trx, bool single_slot); bool state_is(enum tbf_fsm_states rhs) const; bool state_is_not(enum tbf_fsm_states rhs) const; bool dl_ass_state_is(enum tbf_dl_ass_fsm_states rhs) const; bool ul_ass_state_is(enum tbf_ul_ass_fsm_states rhs) const; void poll_sched_set(const char *file, int line); void poll_sched_unset(const char *file, int line); - bool check_n_clear(uint8_t state_flag); const char *state_name() const; - const char *name() const; + const char *name(bool enclousure = true) const; struct msgb *create_dl_ass(uint32_t fn, uint8_t ts); @@ -183,29 +180,19 @@ struct gprs_rlcmac_tbf { bool n_inc(enum tbf_counters n); void n_reset(enum tbf_counters n); - int update(); void handle_timeout(); void stop_timers(const char *reason); bool timers_pending(enum tbf_timers t); void t_stop(enum tbf_timers t, const char *reason); void t_start(enum tbf_timers t, int T, const char *reason, bool force, const char *file, unsigned line); - int establish_dl_tbf_on_pacch(); - - int check_polling(uint32_t fn, uint8_t ts, - uint32_t *poll_fn, unsigned int *rrbp) const; - void set_polling(uint32_t poll_fn, uint8_t ts, enum pdch_ulc_tbf_poll_reason reason); void poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_fn, enum pdch_ulc_tbf_poll_reason reason); /** tlli handling */ uint32_t tlli() const; bool is_tlli_valid() const; - /** MS updating */ - void update_ms(uint32_t tlli, enum gprs_rlcmac_tbf_direction); - uint8_t tfi() const; - bool is_tfi_assigned() const; const char *imsi() const; uint8_t ta() const; @@ -217,8 +204,6 @@ struct gprs_rlcmac_tbf { uint8_t dl_slots() const; uint8_t ul_slots() const; - bool is_control_ts(uint8_t ts) const; - /* EGPRS */ bool is_egprs_enabled() const; @@ -227,10 +212,7 @@ struct gprs_rlcmac_tbf { enum gprs_rlcmac_tbf_direction direction; struct gprs_rlcmac_trx *trx; - uint8_t first_ts; /* first TS used by TBF */ - uint8_t first_common_ts; /* first TS where the phone can send and - receive simultaniously */ - uint8_t control_ts; /* timeslot control messages and polling */ + struct gprs_rlcmac_pdch *control_ts; /* timeslot control messages and polling */ struct gprs_rlcmac_pdch *pdch[8]; /* list of PDCHs allocated to TBF */ gprs_llc m_llc; @@ -261,26 +243,26 @@ struct gprs_rlcmac_tbf { time_t m_created_ts; struct rate_ctr_group *m_ctrs; - struct tbf_fsm_ctx state_fsm; + struct osmo_fsm_inst *state_fi; struct tbf_ul_ass_fsm_ctx ul_ass_fsm; - struct tbf_ul_ass_fsm_ctx dl_ass_fsm; + struct tbf_dl_ass_fsm_ctx dl_ass_fsm; struct llist_item m_ms_list; struct llist_item m_trx_list; protected: void merge_and_clear_ms(GprsMs *old_ms); + void enable_egprs(void); gprs_llc_queue *llc_queue(); const gprs_llc_queue *llc_queue() const; struct GprsMs *m_ms; private: - void enable_egprs(); bool m_egprs_enabled; struct osmo_timer_list Tarr[T_MAX]; uint8_t Narr[N_MAX]; - mutable char m_name_buf[60]; + mutable char m_name_buf[128]; }; inline bool gprs_rlcmac_tbf::state_is(enum tbf_fsm_states rhs) const @@ -306,17 +288,7 @@ inline bool gprs_rlcmac_tbf::state_is_not(enum tbf_fsm_states rhs) const inline const char *gprs_rlcmac_tbf::state_name() const { - return osmo_fsm_inst_state_name(state_fsm.fi); -} - -inline bool gprs_rlcmac_tbf::check_n_clear(uint8_t state_flag) -{ - if ((state_fsm.state_flags & (1 << state_flag))) { - state_fsm.state_flags &= ~(1 << state_flag); - return true; - } - - return false; + return osmo_fsm_inst_state_name(state_fi); } inline GprsMs *gprs_rlcmac_tbf::ms() const @@ -329,16 +301,6 @@ inline bool gprs_rlcmac_tbf::is_tlli_valid() const return tlli() != GSM_RESERVED_TMSI; } -inline bool gprs_rlcmac_tbf::is_tfi_assigned() const -{ - /* The TBF is established or has been assigned by a IMM.ASS for - * download */ - return state_fsm.fi->state > TBF_ST_ASSIGN || - (direction == GPRS_RLCMAC_DL_TBF && - state_fsm.fi->state == TBF_ST_ASSIGN && - (state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH))); -} - inline uint8_t gprs_rlcmac_tbf::tfi() const { return m_tfi; diff --git a/src/tbf_dl.cpp b/src/tbf_dl.cpp index 05d5ad36..dc35b844 100644 --- a/src/tbf_dl.cpp +++ b/src/tbf_dl.cpp @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <bts.h> @@ -31,9 +27,9 @@ #include <decoding.h> #include <encoding.h> #include <gprs_ms.h> -#include <gprs_ms_storage.h> #include <llc.h> #include "pcu_utils.h" +#include "alloc_algo.h" extern "C" { #include <osmocom/core/msgb.h> @@ -94,26 +90,14 @@ static const struct rate_ctr_group_desc tbf_dl_egprs_ctrg_desc = { tbf_dl_egprs_ctr_description, }; -static void llc_timer_cb(void *_tbf) -{ - struct gprs_rlcmac_dl_tbf *tbf = (struct gprs_rlcmac_dl_tbf *)_tbf; - - if (tbf->state_is_not(TBF_ST_FLOW)) - return; - - LOGPTBFDL(tbf, LOGL_DEBUG, "LLC receive timeout, requesting DL ACK\n"); - - tbf->request_dl_ack(); -} - gprs_rlcmac_dl_tbf::BandWidth::BandWidth() : dl_bw_octets(0), dl_throughput(0), dl_loss_lost(0), dl_loss_received(0) { - timespecclear(&dl_bw_tv); - timespecclear(&dl_loss_tv); + osmo_clock_gettime(CLOCK_MONOTONIC, &dl_bw_tv); + osmo_clock_gettime(CLOCK_MONOTONIC, &dl_loss_tv); } static int dl_tbf_dtor(struct gprs_rlcmac_dl_tbf *tbf) @@ -122,10 +106,9 @@ static int dl_tbf_dtor(struct gprs_rlcmac_dl_tbf *tbf) return 0; } -struct gprs_rlcmac_dl_tbf *tbf_alloc_dl_tbf(struct gprs_rlcmac_bts *bts, GprsMs *ms, int8_t use_trx, bool single_slot) +struct gprs_rlcmac_dl_tbf *dl_tbf_alloc(struct gprs_rlcmac_bts *bts, struct GprsMs *ms) { struct gprs_rlcmac_dl_tbf *tbf; - int rc; OSMO_ASSERT(ms != NULL); @@ -140,15 +123,7 @@ struct gprs_rlcmac_dl_tbf *tbf_alloc_dl_tbf(struct gprs_rlcmac_bts *bts, GprsMs talloc_set_destructor(tbf, dl_tbf_dtor); new (tbf) gprs_rlcmac_dl_tbf(bts, ms); - rc = tbf->setup(use_trx, single_slot); - /* if no resource */ - if (rc < 0) { - talloc_free(tbf); - return NULL; - } - if (tbf->is_egprs_enabled()) { - tbf->set_window_size(); tbf->m_dl_egprs_ctrs = rate_ctr_group_alloc(tbf, &tbf_dl_egprs_ctrg_desc, tbf->m_ctrs->idx); @@ -167,19 +142,13 @@ struct gprs_rlcmac_dl_tbf *tbf_alloc_dl_tbf(struct gprs_rlcmac_bts *bts, GprsMs return NULL; } } - - llist_add(tbf_trx_list((struct gprs_rlcmac_tbf *)tbf), &tbf->trx->dl_tbfs); bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_DL_ALLOCATED); - osmo_clock_gettime(CLOCK_MONOTONIC, &tbf->m_bw.dl_bw_tv); - osmo_clock_gettime(CLOCK_MONOTONIC, &tbf->m_bw.dl_loss_tv); - return tbf; } gprs_rlcmac_dl_tbf::~gprs_rlcmac_dl_tbf() { - osmo_timer_del(&m_llc_timer); if (is_egprs_enabled()) { rate_ctr_group_free(m_dl_egprs_ctrs); } else { @@ -193,91 +162,23 @@ gprs_rlcmac_dl_tbf::gprs_rlcmac_dl_tbf(struct gprs_rlcmac_bts *bts_, GprsMs *ms) m_tx_counter(0), m_dl_ack_requested(false), m_last_dl_poll_fn(-1), + m_last_dl_poll_ack_lost(false), m_last_dl_drained_fn(-1), + m_first_dl_ack_rcvd(false), m_dl_gprs_ctrs(NULL), m_dl_egprs_ctrs(NULL) { - memset(&m_llc_timer, 0, sizeof(m_llc_timer)); - osmo_timer_setup(&m_llc_timer, llc_timer_cb, this); -} - -void gprs_rlcmac_dl_tbf::start_llc_timer() -{ - if (the_pcu->vty.llc_idle_ack_csec > 0) { - struct timespec tv; - csecs_to_timespec(the_pcu->vty.llc_idle_ack_csec, &tv); - osmo_timer_schedule(&m_llc_timer, tv.tv_sec, tv.tv_nsec / 1000); - } -} - -int gprs_rlcmac_dl_tbf::append_data(uint16_t pdu_delay_csec, - const uint8_t *data, uint16_t len) -{ - struct timespec expire_time; - - LOGPTBFDL(this, LOGL_DEBUG, "appending %u bytes\n", len); - - struct msgb *llc_msg = msgb_alloc(len, "llc_pdu_queue"); - if (!llc_msg) - return -ENOMEM; - - gprs_llc_queue::calc_pdu_lifetime(bts, pdu_delay_csec, &expire_time); - memcpy(msgb_put(llc_msg, len), data, len); - llc_queue()->enqueue(llc_msg, &expire_time); - start_llc_timer(); - - if (state_is(TBF_ST_WAIT_RELEASE)) { - LOGPTBFDL(this, LOGL_DEBUG, "in WAIT RELEASE state (T3193), so reuse TBF\n"); - establish_dl_tbf_on_pacch(); - } - - return 0; -} - -static int tbf_new_dl_assignment(struct gprs_rlcmac_bts *bts, GprsMs *ms, - struct gprs_rlcmac_dl_tbf **tbf) -{ - bool ss; - int8_t use_trx; - struct gprs_rlcmac_ul_tbf *ul_tbf = NULL, *old_ul_tbf; - struct gprs_rlcmac_dl_tbf *dl_tbf = NULL; - - ul_tbf = ms_ul_tbf(ms); - - /* 3GPP TS 44.060 sec 7.1.3.1 Initiation of the Packet resource request procedure: - * "Furthermore, the mobile station shall not respond to PACKET DOWNLINK ASSIGNMENT - * or MULTIPLE TBF DOWNLINK ASSIGNMENT messages before contention resolution is - * completed on the mobile station side." */ - if (ul_tbf && ul_tbf->m_contention_resolution_done - && !tbf_ul_ack_waiting_cnf_final_ack(ul_tbf)) { - use_trx = ul_tbf->trx->trx_no; - ss = false; - old_ul_tbf = ul_tbf; - } else { - use_trx = -1; - ss = true; /* PCH assignment only allows one timeslot */ - old_ul_tbf = NULL; - } + memset(&state_fsm, 0, sizeof(state_fsm)); + state_fsm.dl_tbf = this; + state_fi = osmo_fsm_inst_alloc(&tbf_dl_fsm, this, &state_fsm, LOGL_INFO, NULL); + OSMO_ASSERT(state_fi); - // Create new TBF (any TRX) -/* FIXME: Copy and paste with alloc_ul_tbf */ - /* set number of downlink slots according to multislot class */ - dl_tbf = tbf_alloc_dl_tbf(bts, ms, use_trx, ss); + INIT_LLIST_HEAD(&this->tx_llc_until_first_dl_ack_rcvd); - if (!dl_tbf) { - LOGPMS(ms, DTBF, LOGL_NOTICE, "No PDCH resource\n"); - return -EBUSY; - } - - LOGPTBFDL(dl_tbf, LOGL_DEBUG, "[DOWNLINK] START\n"); - - /* trigger downlink assignment and set state to ASSIGN. - * we don't use old_downlink, so the possible uplink is used - * to trigger downlink assignment. if there is no uplink, - * AGCH is used. */ - dl_tbf->trigger_ass(old_ul_tbf); - *tbf = dl_tbf; - return 0; + /* This has to be called in child constructor because enable_egprs() + * uses the window() virtual function which is dependent on subclass. */ + if (ms_mode(m_ms) != GPRS) + enable_egprs(); } /** @@ -289,41 +190,35 @@ int dl_tbf_handle(struct gprs_rlcmac_bts *bts, const uint16_t delay_csec, const uint8_t *data, const uint16_t len) { - struct gprs_rlcmac_dl_tbf *dl_tbf = NULL; int rc; GprsMs *ms, *ms_old; + bool ms_allocated = false; /* check for existing TBF */ - ms = bts_ms_store(bts)->get_ms(tlli, tlli_old, imsi); - - if (ms && strlen(ms_imsi(ms)) == 0) { - ms_old = bts_ms_store(bts)->get_ms(0, 0, imsi); - if (ms_old && ms_old != ms) { - /* The TLLI has changed (RAU), so there are two MS - * objects for the same MS */ - LOGP(DTBF, LOGL_NOTICE, - "There is a new MS object for the same MS: (0x%08x, '%s') -> (0x%08x, '%s')\n", - ms_tlli(ms_old), ms_imsi(ms_old), ms_tlli(ms), ms_imsi(ms)); - - ms_ref(ms_old); - - if (!ms_dl_tbf(ms) && ms_dl_tbf(ms_old)) { + ms = bts_get_ms(bts, tlli, tlli_old, imsi); + + /* If we got MS by TLLI above let's see if we already have another MS + * object identified by IMSI and merge them */ + if (ms) { + if (!ms_imsi_is_valid(ms) && imsi) { + ms_old = bts_get_ms_by_imsi(bts, imsi); + if (ms_old && ms_old != ms) { + /* The TLLI has changed (RAU), so there are two MS + * objects for the same MS */ LOGP(DTBF, LOGL_NOTICE, - "IMSI %s, old TBF %s: moving DL TBF to new MS object\n", - imsi, ms_dl_tbf(ms_old)->name()); - dl_tbf = ms_dl_tbf(ms_old); - /* Move the DL TBF to the new MS */ - dl_tbf->set_ms(ms); + "There is a new MS object for the same MS: (0x%08x, '%s') -> (0x%08x, '%s')\n", + ms_tlli(ms_old), ms_imsi(ms_old), ms_tlli(ms), ms_imsi(ms)); + ms_merge_and_clear_ms(ms, ms_old); + /* old_ms may no longer be available here */ } - ms_merge_and_clear_ms(ms, ms_old); - - ms_unref(ms_old); } + } else { + ms = ms_alloc(bts, __func__); + /* Remember we have to unref the alloc reference at the end: */ + ms_allocated = true; } - - if (!ms) - ms = bts_alloc_ms(bts, ms_class, egprs_ms_class); - ms_set_imsi(ms, imsi); + if (imsi) + ms_set_imsi(ms, imsi); ms_confirm_tlli(ms, tlli); if (!ms_ms_class(ms) && ms_class) { ms_set_ms_class(ms, ms_class); @@ -332,95 +227,15 @@ int dl_tbf_handle(struct gprs_rlcmac_bts *bts, ms_set_egprs_ms_class(ms, egprs_ms_class); } - dl_tbf = ms_dl_tbf(ms); - if (!dl_tbf) { - rc = tbf_new_dl_assignment(bts, ms, &dl_tbf); - if (rc < 0) - return rc; + rc = ms_append_llc_dl_data(ms, delay_csec, data, len); + if (ms_allocated) { + ms_unref(ms, __func__); + /* Here "ms" may be freed if ms_append_llc_dl_data() failed to + * allocate a DL TBF and it has no more TBFs attached */ } - - rc = dl_tbf->append_data(delay_csec, data, len); - return rc; } -struct msgb *gprs_rlcmac_dl_tbf::llc_dequeue(bssgp_bvc_ctx *bctx) -{ - struct msgb *msg; - struct timespec tv_now, tv_now2; - uint32_t octets = 0, frames = 0; - struct timespec hyst_delta = {0, 0}; - const unsigned keep_small_thresh = 60; - const MetaInfo *info; - - if (the_pcu->vty.llc_discard_csec) - csecs_to_timespec(the_pcu->vty.llc_discard_csec, &hyst_delta); - - osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now); - timespecadd(&tv_now, &hyst_delta, &tv_now2); - - while ((msg = llc_queue()->dequeue(&info))) { - const struct timespec *tv_disc = &info->expire_time; - const struct timespec *tv_recv = &info->recv_time; - - gprs_bssgp_update_queue_delay(tv_recv, &tv_now); - - if (ms() && ms_codel_state(ms())) { - int bytes = llc_queue_octets(llc_queue()); - if (gprs_codel_control(ms_codel_state(ms()), - tv_recv, &tv_now, bytes)) - goto drop_frame; - } - - /* Is the age below the low water mark? */ - if (!gprs_llc_queue::is_frame_expired(&tv_now2, tv_disc)) - break; - - /* Is the age below the high water mark */ - if (!gprs_llc_queue::is_frame_expired(&tv_now, tv_disc)) { - /* Has the previous message not been dropped? */ - if (frames == 0) - break; - - /* Hysteresis mode, try to discard LLC messages until - * the low water mark has been reached */ - - /* Check whether to abort the hysteresis mode */ - - /* Is the frame small, perhaps only a TCP ACK? */ - if (msg->len <= keep_small_thresh) - break; - - /* Is it a GMM message? */ - if (!gprs_llc::is_user_data_frame(msg->data, msg->len)) - break; - } - - bts_do_rate_ctr_inc(bts, CTR_LLC_FRAME_TIMEDOUT); -drop_frame: - frames++; - octets += msg->len; - msgb_free(msg); - bts_do_rate_ctr_inc(bts, CTR_LLC_FRAME_DROPPED); - continue; - } - - if (frames) { - LOGPTBFDL(this, LOGL_NOTICE, "Discarding LLC PDU " - "because lifetime limit reached, " - "count=%u new_queue_size=%zu\n", - frames, llc_queue_size(llc_queue())); - if (frames > 0xff) - frames = 0xff; - if (octets > 0xffffff) - octets = 0xffffff; - if (bctx) - bssgp_tx_llc_discarded(bctx, tlli(), frames, octets); - } - - return msg; -} - bool gprs_rlcmac_dl_tbf::restart_bsn_cycle() { /* If V(S) == V(A) and finished state, we would have received @@ -578,7 +393,7 @@ int gprs_rlcmac_dl_tbf::take_next_bsn(uint32_t fn, * Create DL data block * The messages are fragmented and forwarded as data blocks. */ -struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block(uint32_t fn, uint8_t ts, enum mcs_kind req_mcs_kind) +struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block(uint32_t fn, const struct gprs_rlcmac_pdch *pdch, enum mcs_kind req_mcs_kind) { int bsn, bsn2 = -1; bool may_combine; @@ -593,35 +408,132 @@ struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block(uint32_t fn, uint8_t ts, if (may_combine) bsn2 = take_next_bsn(fn, bsn, req_mcs_kind, &may_combine); - return create_dl_acked_block(fn, ts, bsn, bsn2); + return create_dl_acked_block(fn, pdch, bsn, bsn2); +} + +void gprs_rlcmac_dl_tbf::apply_allocated_resources(const struct alloc_resources_res *res) +{ + uint8_t ts; + + if (this->trx) + llist_del(&this->m_trx_list.list); + + llist_add(&this->m_trx_list.list, &res->trx->dl_tbfs); + + this->trx = res->trx; + this->upgrade_to_multislot = res->upgrade_to_multislot; + + for (ts = 0; ts < ARRAY_SIZE(trx->pdch); ts++) { + struct gprs_rlcmac_pdch *pdch = &trx->pdch[ts]; + OSMO_ASSERT(!this->pdch[pdch->ts_no]); + if (!(res->ass_slots_mask & (1 << ts))) + continue; + LOGPTBFDL(this, LOGL_DEBUG, "Assigning TS=%u TFI=%d\n", + ts, res->tfi); + + this->m_tfi = res->tfi; + + this->pdch[pdch->ts_no] = pdch; + pdch->attach_tbf(this); + } + + /* assign initial control ts */ + tbf_assign_control_ts(this); + + LOGPTBF(this, LOGL_INFO, + "Allocated: trx = %d, ul_slots = %02x, dl_slots = %02x\n", + this->trx->trx_no, ul_slots(), dl_slots()); + + if (tbf_is_egprs_enabled(this)) + this->set_window_size(); + + tbf_update_state_fsm_name(this); } -/* depending on the current TBF, we assign on PACCH or AGCH */ -void gprs_rlcmac_dl_tbf::trigger_ass(struct gprs_rlcmac_tbf *old_tbf) +void dl_tbf_apply_allocated_resources(struct gprs_rlcmac_dl_tbf *dl_tbf, const struct alloc_resources_res *res) { - uint16_t pgroup; + dl_tbf->apply_allocated_resources(res); +} + +/* old_tbf (UL TBF or DL TBF) will send a Pkt Dl Ass on PACCH to assign tbf. + * Note: It is possible that "tbf == old_tbf" if the TBF is being updated. This can + * happen when we first assign over PCH (only single slot is possible) and we want + * to upgrade the DL-TBF to be multislot. See code calling tbf_update() for more + * information. + */ +void dl_tbf_trigger_ass_on_pacch(struct gprs_rlcmac_dl_tbf *tbf, struct gprs_rlcmac_tbf *old_tbf) +{ + OSMO_ASSERT(tbf); + OSMO_ASSERT(old_tbf); /* stop pending timer */ - stop_timers("assignment (DL-TBF)"); + tbf_stop_timers(tbf, "DL assignment (PACCH)"); - /* check for downlink tbf: */ - if (old_tbf) { - LOGPTBFDL(this, LOGL_DEBUG, "Send downlink assignment on PACCH, because %s exists\n", old_tbf->name()); - osmo_fsm_inst_dispatch(old_tbf->dl_ass_fsm.fi, TBF_DL_ASS_EV_SCHED_ASS, NULL); + LOGPTBFDL(tbf, LOGL_DEBUG, "Send downlink assignment on PACCH, because %s exists\n", old_tbf->name()); + osmo_fsm_inst_dispatch(old_tbf->dl_ass_fsm.fi, TBF_DL_ASS_EV_SCHED_ASS, NULL); - /* change state */ - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_ASSIGN_ADD_PACCH, NULL); - } else { - LOGPTBFDL(this, LOGL_DEBUG, "Send downlink assignment on PCH, no TBF exist (IMSI=%s)\n", - imsi()); + /* change state */ + osmo_fsm_inst_dispatch(tbf->state_fi, TBF_EV_ASSIGN_ADD_PACCH, NULL); + +} + +void dl_tbf_trigger_ass_on_pch(struct gprs_rlcmac_dl_tbf *tbf) +{ + /* stop pending timer */ + struct GprsMs *ms = tbf_ms(tbf); + + tbf_stop_timers(tbf, "DL assignment (PCH)"); + + LOGPTBFDL(tbf, LOGL_DEBUG, "Send downlink assignment on PCH, no TBF exist (IMSI=%s)\n", ms_imsi(ms)); + + /* change state */ + osmo_fsm_inst_dispatch(tbf->state_fi, TBF_EV_ASSIGN_ADD_CCCH, NULL); +} + +int dl_tbf_upgrade_to_multislot(struct gprs_rlcmac_dl_tbf *dl_tbf) +{ + int rc; + struct gprs_rlcmac_tbf *tbf = dl_tbf_as_tbf(dl_tbf); + struct gprs_rlcmac_trx *trx = tbf_get_trx(dl_tbf); + struct gprs_rlcmac_bts *bts = trx->bts; + struct GprsMs *ms = tbf->ms(); - /* change state */ - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_ASSIGN_ADD_CCCH, NULL); + LOGPTBFDL(dl_tbf, LOGL_DEBUG, "Upgrade to multislot\n"); - /* send immediate assignment */ - if ((pgroup = imsi2paging_group(imsi())) > 999) - LOGPTBFDL(this, LOGL_ERROR, "IMSI to paging group failed! (%s)\n", imsi()); - bts_snd_dl_ass(bts, this, pgroup); + tbf_unlink_pdch(dl_tbf); + + const struct alloc_resources_req req = { + .bts = bts, + .ms = ms, + .direction = tbf_direction(tbf), + .single = false, + .use_trx = -1, + }; + struct alloc_resources_res res = {}; + + rc = the_pcu->alloc_algorithm(&req, &res); + /* if no resource */ + if (rc < 0) { + LOGPTBFDL(dl_tbf, LOGL_ERROR, "No resources allocated during upgrade to multislot!\n"); + bts_do_rate_ctr_inc(bts, CTR_TBF_ALLOC_FAIL); + return rc; } + + /* Update MS, really allocate the resources */ + if (res.reserved_ul_slots != ms_reserved_ul_slots(ms) || + res.reserved_dl_slots != ms_reserved_dl_slots(ms)) { + /* The reserved slots have changed, update the MS */ + ms_set_reserved_slots(ms, res.trx, res.reserved_ul_slots, res.reserved_dl_slots); + } + ms_set_first_common_ts(ms, res.first_common_ts); + + /* Apply allocated resources to TBF: */ + dl_tbf_apply_allocated_resources(dl_tbf, &res); + + /* Note: No need to call ms_attach_tbf(), tbf is already attached to the MS */ + + /* Now trigger the assignment using the pre-existing TBF: */ + dl_tbf_trigger_ass_on_pacch(dl_tbf, tbf); + return 0; } void gprs_rlcmac_dl_tbf::schedule_next_frame() @@ -632,13 +544,13 @@ void gprs_rlcmac_dl_tbf::schedule_next_frame() return; /* dequeue next LLC frame, if any */ - msg = llc_dequeue(bts->pcu->bssgp.bctx); + msg = llc_queue_dequeue(llc_queue(), &m_llc.prio, &m_llc.meta_info); if (!msg) return; LOGPTBFDL(this, LOGL_DEBUG, "Dequeue next LLC (len=%d)\n", msg->len); - m_llc.put_frame(msg->data, msg->len); + llc_put_frame(&m_llc, msg->data, msg->len); bts_do_rate_ctr_inc(bts, CTR_LLC_FRAME_SCHED); msgb_free(msg); m_last_dl_drained_fn = -1; @@ -710,7 +622,7 @@ int gprs_rlcmac_dl_tbf::create_new_bsn(const uint32_t fn, enum CodingScheme cs) is_final = llc_queue_size(llc_queue()) == 0 && !keep_open(fn); if (is_final) { rdbi->cv = 0; - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_LAST_DL_DATA_SENT, NULL); + osmo_fsm_inst_dispatch(this->state_fi, TBF_EV_LAST_DL_DATA_SENT, NULL); } if (mcs_is_edge(cs)) { @@ -730,7 +642,7 @@ int gprs_rlcmac_dl_tbf::create_new_bsn(const uint32_t fn, enum CodingScheme cs) * "Delayed release of downlink Temporary Block Flow" */ /* A header will need to by added, so we just need * space-1 octets */ - m_llc.put_dummy_frame(space - 1); + llc_put_dummy_frame(&m_llc, space - 1); LOGPTBFDL(this, LOGL_DEBUG, "Empty chunk, added LLC dummy command of size %d, drained_since=%d\n", @@ -751,11 +663,20 @@ int gprs_rlcmac_dl_tbf::create_new_bsn(const uint32_t fn, enum CodingScheme cs) LOGPTBFDL(this, LOGL_DEBUG, "Complete DL frame, len=%d\n", llc_frame_length(&m_llc)); gprs_rlcmac_dl_bw(this, llc_frame_length(&m_llc)); bts_do_rate_ctr_add(bts, CTR_LLC_DL_BYTES, llc_frame_length(&m_llc)); - m_llc.reset(); + + /* Keep transmitted LLC PDUs until first ACK to avoid lossing them if MS is not there. */ + if (!this->m_first_dl_ack_rcvd) { + struct gprs_dl_llc_llist_item *llc_it = talloc(this, struct gprs_dl_llc_llist_item); + memcpy(&llc_it->llc, &m_llc, sizeof(llc_it->llc)); + /* Prepend to list to store them in inverse order of transmission, see + * dl_tbf_copy_unacked_pdus_to_llc_queue() for the complete picture. */ + llist_add(&llc_it->list, &this->tx_llc_until_first_dl_ack_rcvd); + } + llc_reset(&m_llc); if (is_final) { request_dl_ack(); - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_LAST_DL_DATA_SENT, NULL); + osmo_fsm_inst_dispatch(this->state_fi, TBF_EV_LAST_DL_DATA_SENT, NULL); } /* dequeue next LLC frame, if any */ @@ -773,7 +694,7 @@ int gprs_rlcmac_dl_tbf::create_new_bsn(const uint32_t fn, enum CodingScheme cs) } struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block( - const uint32_t fn, const uint8_t ts, + const uint32_t fn, const struct gprs_rlcmac_pdch *pdch, int index, int index2) { uint8_t *msg_data; @@ -940,13 +861,14 @@ struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block( m_last_dl_poll_fn = fn; /* poll after POLL_ACK_AFTER_FRAMES frames, or when final block is tx or - * when last polled DL ACK/NACK was lost. */ - if (need_poll_for_dl_ack_nack()) { + * when last polled DL ACK/NACK was lost. Always do so in the control TS. */ + if (tbf_is_control_ts(dl_tbf_as_tbf(this), pdch) && + need_poll_for_dl_ack_nack()) { if (m_dl_ack_requested) { LOGPTBFDL(this, LOGL_DEBUG, "Scheduling Ack/Nack polling, because it was requested explicitly " "(e.g. first final block sent).\n"); - } else if (state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_TO_DL_ACK)) { + } else if (m_last_dl_poll_ack_lost) { LOGPTBFDL(this, LOGL_DEBUG, "Scheduling Ack/Nack polling, because polling timed out.\n"); } else { @@ -955,22 +877,22 @@ struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block( POLL_ACK_AFTER_FRAMES); } - rc = check_polling(fn, ts, &new_poll_fn, &rrbp); + rc = tbf_check_polling(this, pdch, fn, &new_poll_fn, &rrbp); if (rc >= 0) { - set_polling(new_poll_fn, ts, PDCH_ULC_POLL_DL_ACK); + tbf_set_polling(this, pdch, new_poll_fn, PDCH_ULC_POLL_DL_ACK); LOGPTBFDL(this, LOGL_DEBUG, "Scheduled DL Acknowledgement polling on PACCH (FN=%d, TS=%d)\n", - new_poll_fn, ts); + new_poll_fn, pdch->ts_no); m_tx_counter = 0; /* start timer whenever we send the final block */ if (is_final) T_START(this, T3191, 3191, "final block (DL-TBF)", true); - state_fsm.state_flags &= ~(1 << GPRS_RLCMAC_FLAG_TO_DL_ACK); /* clear poll timeout flag */ - /* Clear request flag */ m_dl_ack_requested = false; + /* clear poll timeout flag */ + m_last_dl_poll_ack_lost = false; /* set polling in header */ rlc.rrbp = rrbp; @@ -980,7 +902,7 @@ struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block( LOGPTBFDL(this, LOGL_INFO, "Scheduled Ack/Nack polling on FN=%d, TS=%d\n", - new_poll_fn, ts); + new_poll_fn, pdch->ts_no); } } @@ -1142,8 +1064,7 @@ int gprs_rlcmac_dl_tbf::update_window(unsigned first_bsn, int gprs_rlcmac_dl_tbf::rcvd_dl_final_ack() { uint16_t received; - - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_FINAL_ACK_RECVD, NULL); + int rc; /* range V(A)..V(S)-1 */ received = m_window.count_unacked(); @@ -1152,12 +1073,9 @@ int gprs_rlcmac_dl_tbf::rcvd_dl_final_ack() m_tx_counter = 0; m_window.reset(); - /* check for LLC PDU in the LLC Queue */ - if (llc_queue_size(llc_queue()) > 0) - /* we have more data so we will re-use this tbf */ - establish_dl_tbf_on_pacch(); + rc = osmo_fsm_inst_dispatch(this->state_fi, TBF_EV_FINAL_ACK_RECVD, NULL); - return 0; + return rc; } int gprs_rlcmac_dl_tbf::rcvd_dl_ack(bool final_ack, unsigned first_bsn, @@ -1166,8 +1084,16 @@ int gprs_rlcmac_dl_tbf::rcvd_dl_ack(bool final_ack, unsigned first_bsn, int rc; LOGPTBFDL(this, LOGL_DEBUG, "downlink acknowledge\n"); - state_fsm.state_flags |= (1 << GPRS_RLCMAC_FLAG_DL_ACK); - state_fsm.state_flags &= ~(1 << GPRS_RLCMAC_FLAG_TO_DL_ACK); + if (m_first_dl_ack_rcvd == false) { + /* MS is there, free temporarily stored transmitted LLC PDUs */ + struct gprs_dl_llc_llist_item *llc_it; + while ((llc_it = llist_first_entry_or_null(&this->tx_llc_until_first_dl_ack_rcvd, struct gprs_dl_llc_llist_item, list))) { + llist_del(&llc_it->list); + talloc_free(llc_it); + } + m_first_dl_ack_rcvd = true; + } + m_last_dl_poll_ack_lost = false; /* reset N3105 */ n_reset(N3105); @@ -1196,12 +1122,48 @@ void gprs_rlcmac_dl_tbf::request_dl_ack() m_dl_ack_requested = true; } +void dl_tbf_request_dl_ack(struct gprs_rlcmac_dl_tbf *dl_tbf) { + dl_tbf->request_dl_ack(); +} + +bool dl_tbf_first_dl_ack_rcvd(const struct gprs_rlcmac_dl_tbf *tbf) +{ + return tbf->m_first_dl_ack_rcvd; +} + +/* Copy back to GprsMs' llc_queue the LLC PDUs previously dequeued and never + * fully ACKED at the MS side. + * FIXME: For now, only blocks transmitted and without first ever DL ACK are + * copied, because we have no way yet to track LLC PDUs once they are converted + * to RLC blocks. This is however enough to cover the case where a DL TBF is + * assigned over PCH and the MS never answers. + */ +void dl_tbf_copy_unacked_pdus_to_llc_queue(struct gprs_rlcmac_dl_tbf *tbf) +{ + struct GprsMs *ms = tbf_ms(dl_tbf_as_tbf(tbf)); + struct gprs_dl_llc_llist_item *llc_it; + + /* If we have LLC PDU still being transmitted, prepend it first to the queue: */ + if (llc_frame_length(&tbf->m_llc) > 0) + llc_queue_merge_prepend(&ms->llc_queue, &tbf->m_llc); + + /* Iterate over the list of totally transmitted LLC PDUs and merge them + * into the queue. The items in the list are in inverse order of + * transmission, hence when popping from here and enqueueing (prepending) + * back to the llc_queue it ends up in the exact same initial order. */ + while ((llc_it = llist_first_entry_or_null(&tbf->tx_llc_until_first_dl_ack_rcvd, struct gprs_dl_llc_llist_item, list))) { + llist_del(&llc_it->list); + llc_queue_merge_prepend(&ms->llc_queue, &llc_it->llc); + talloc_free(llc_it); + } +} + /* Does this DL TBF require to poll the MS for DL ACK/NACK? */ bool gprs_rlcmac_dl_tbf::need_poll_for_dl_ack_nack() const { /* poll after POLL_ACK_AFTER_FRAMES frames, or when final block is tx or * when last polled DL ACK/NACK was lost. */ - return state_fsm.state_flags & (1 << GPRS_RLCMAC_FLAG_TO_DL_ACK) || + return m_last_dl_poll_ack_lost || m_tx_counter >= POLL_ACK_AFTER_FRAMES || m_dl_ack_requested; } @@ -1442,7 +1404,7 @@ void gprs_rlcmac_dl_tbf::update_coding_scheme_counter_dl(enum CodingScheme cs) } } -struct gprs_rlcmac_dl_tbf *as_dl_tbf(struct gprs_rlcmac_tbf *tbf) +struct gprs_rlcmac_dl_tbf *tbf_as_dl_tbf(struct gprs_rlcmac_tbf *tbf) { if (tbf && tbf->direction == GPRS_RLCMAC_DL_TBF) return static_cast<gprs_rlcmac_dl_tbf *>(tbf); @@ -1450,7 +1412,10 @@ struct gprs_rlcmac_dl_tbf *as_dl_tbf(struct gprs_rlcmac_tbf *tbf) return NULL; } -void tbf_dl_trigger_ass(struct gprs_rlcmac_dl_tbf *tbf, struct gprs_rlcmac_tbf *old_tbf) +const struct gprs_rlcmac_dl_tbf *tbf_as_dl_tbf_const(const struct gprs_rlcmac_tbf *tbf) { - return tbf->trigger_ass(old_tbf); + if (tbf && tbf->direction == GPRS_RLCMAC_DL_TBF) + return static_cast<const gprs_rlcmac_dl_tbf *>(tbf); + else + return NULL; } diff --git a/src/tbf_dl.h b/src/tbf_dl.h index d20ad75f..59a03565 100644 --- a/src/tbf_dl.h +++ b/src/tbf_dl.h @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -22,9 +18,18 @@ #ifdef __cplusplus #include "tbf.h" +#include "rlc_window_dl.h" #include <stdint.h> +#ifdef __cplusplus +extern "C" { +#endif +#include <tbf_fsm.h> +#ifdef __cplusplus +} +#endif + /* * TBF instance */ @@ -38,17 +43,20 @@ enum tbf_dl_prio { DL_PRIO_CONTROL, /* a control block needs to be sent */ }; +struct gprs_dl_llc_llist_item { + struct llist_head list; /* this item added in dl_tbf->tx_llc_until_first_dl_ack_rcvd */ + struct gprs_llc llc; +}; + struct gprs_rlcmac_dl_tbf : public gprs_rlcmac_tbf { gprs_rlcmac_dl_tbf(struct gprs_rlcmac_bts *bts, GprsMs *ms); ~gprs_rlcmac_dl_tbf(); gprs_rlc_window *window(); - - int append_data(uint16_t pdu_delay_csec, - const uint8_t *data, uint16_t len); + void apply_allocated_resources(const struct alloc_resources_res *res); int rcvd_dl_ack(bool final_ack, unsigned first_bsn, struct bitvec *rbb); - struct msgb *create_dl_acked_block(uint32_t fn, uint8_t ts, enum mcs_kind req_mcs_kind = EGPRS); - void trigger_ass(struct gprs_rlcmac_tbf *old_tbf); + struct msgb *create_dl_acked_block(uint32_t fn, const gprs_rlcmac_pdch *pdch, + enum mcs_kind req_mcs_kind = EGPRS); void request_dl_ack(); bool need_poll_for_dl_ack_nack() const; @@ -61,8 +69,6 @@ struct gprs_rlcmac_dl_tbf : public gprs_rlcmac_tbf { void set_window_size(); void update_coding_scheme_counter_dl(enum CodingScheme cs); - struct msgb *llc_dequeue(bssgp_bvc_ctx *bctx); - /* Please note that all variables here will be reset when changing * from WAIT RELEASE back to FLOW state (re-use of TBF). * All states that need reset must be in this struct, so this is why @@ -71,7 +77,17 @@ struct gprs_rlcmac_dl_tbf : public gprs_rlcmac_tbf { int32_t m_tx_counter; /* count all transmitted blocks */ bool m_dl_ack_requested; int32_t m_last_dl_poll_fn; + /* Whether we failed to receive ("poll timeout") last PKT CTRL ACK from + * MS polled during DL ACK/NACK with RRBP set in "m_last_dl_poll_fn": */ + bool m_last_dl_poll_ack_lost; int32_t m_last_dl_drained_fn; + /* Whether we receive at least one PKT DL ACK/NACK from MS since this DL TBF was assigned: */ + bool m_first_dl_ack_rcvd; + + /* Keep transmitted LLC PDUs until first ACK to avoid losing them if MS is not there. + * list of gprs_dl_llc_llist_item, stored in inverse order of transmission (last transmitted + * is first in the list ) */ + struct llist_head tx_llc_until_first_dl_ack_rcvd; struct BandWidth { struct timespec dl_bw_tv; /* timestamp for dl bw calculation */ @@ -88,6 +104,8 @@ struct gprs_rlcmac_dl_tbf : public gprs_rlcmac_tbf { struct rate_ctr_group *m_dl_gprs_ctrs; struct rate_ctr_group *m_dl_egprs_ctrs; + struct tbf_dl_fsm_ctx state_fsm; + protected: struct ana_result { unsigned received_packets; @@ -100,13 +118,12 @@ protected: bool *may_combine); bool restart_bsn_cycle(); int create_new_bsn(const uint32_t fn, enum CodingScheme cs); - struct msgb *create_dl_acked_block(const uint32_t fn, const uint8_t ts, - int index, int index2 = -1); + struct msgb *create_dl_acked_block(const uint32_t fn, const struct gprs_rlcmac_pdch *pdch, + int index, int index2 = -1); int update_window(unsigned first_bsn, const struct bitvec *rbb); int rcvd_dl_final_ack(); bool dl_window_stalled() const; void reuse_tbf(); - void start_llc_timer(); int analyse_errors(char *show_rbb, uint8_t ssn, ana_result *res); void schedule_next_frame(); @@ -115,8 +132,6 @@ protected: unsigned int get_egprs_dl_spb_status(int bsn); enum egprs_rlcmac_dl_spb get_egprs_dl_spb(int bsn); - struct osmo_timer_list m_llc_timer; - /* Please note that all variables below will be reset when changing * from WAIT RELEASE back to FLOW state (re-use of TBF). * All states that need reset must be in this struct, so this is why @@ -130,9 +145,6 @@ inline uint16_t gprs_rlcmac_dl_tbf::window_size() const return m_window.ws(); } -struct gprs_rlcmac_dl_tbf *tbf_alloc_dl_tbf(struct gprs_rlcmac_bts *bts, GprsMs *ms, - int8_t use_trx, bool single_slot); - #else /* ifdef __cplusplus */ struct gprs_rlcmac_dl_tbf; #endif @@ -142,7 +154,10 @@ extern "C" { #endif struct gprs_rlcmac_bts; -struct gprs_rlcmac_dl_tbf *as_dl_tbf(struct gprs_rlcmac_tbf *tbf); +struct gprs_rlcmac_dl_tbf *dl_tbf_alloc(struct gprs_rlcmac_bts *bts, struct GprsMs *ms); + +struct gprs_rlcmac_dl_tbf *tbf_as_dl_tbf(struct gprs_rlcmac_tbf *tbf); +const struct gprs_rlcmac_dl_tbf *tbf_as_dl_tbf_const(const struct gprs_rlcmac_tbf *tbf); /* dispatch Unitdata.DL messages */ int dl_tbf_handle(struct gprs_rlcmac_bts *bts, const uint32_t tlli, const uint32_t old_tlli, @@ -150,9 +165,26 @@ int dl_tbf_handle(struct gprs_rlcmac_bts *bts, const uint8_t egprs_ms_class, const uint16_t delay_csec, const uint8_t *data, const uint16_t len); -void tbf_dl_trigger_ass(struct gprs_rlcmac_dl_tbf *tbf, struct gprs_rlcmac_tbf *old_tbf); +void dl_tbf_apply_allocated_resources(struct gprs_rlcmac_dl_tbf *dl_tbf, const struct alloc_resources_res *res); +void dl_tbf_trigger_ass_on_pacch(struct gprs_rlcmac_dl_tbf *tbf, struct gprs_rlcmac_tbf *old_tbf); +void dl_tbf_trigger_ass_on_pch(struct gprs_rlcmac_dl_tbf *tbf); +void dl_tbf_request_dl_ack(struct gprs_rlcmac_dl_tbf *tbf); +bool dl_tbf_first_dl_ack_rcvd(const struct gprs_rlcmac_dl_tbf *tbf); +int dl_tbf_upgrade_to_multislot(struct gprs_rlcmac_dl_tbf *tbf); + +void dl_tbf_copy_unacked_pdus_to_llc_queue(struct gprs_rlcmac_dl_tbf *tbf); + +static inline struct gprs_rlcmac_tbf *dl_tbf_as_tbf(struct gprs_rlcmac_dl_tbf *dl_tbf) +{ + return (struct gprs_rlcmac_tbf *)dl_tbf; +} + +static inline const struct gprs_rlcmac_tbf *dl_tbf_as_tbf_const(const struct gprs_rlcmac_dl_tbf *dl_tbf) +{ + return (const struct gprs_rlcmac_tbf *)dl_tbf; +} -#define LOGPTBFDL(tbf, level, fmt, args...) LOGP(DTBFDL, level, "%s " fmt, tbf_name(tbf), ## args) +#define LOGPTBFDL(dl_tbf, level, fmt, args...) LOGP(DTBFDL, level, "%s " fmt, tbf_name(dl_tbf_as_tbf_const(dl_tbf)), ## args) #ifdef __cplusplus } #endif diff --git a/src/tbf_dl_ass_fsm.c b/src/tbf_dl_ass_fsm.c index 37ced616..d811b711 100644 --- a/src/tbf_dl_ass_fsm.c +++ b/src/tbf_dl_ass_fsm.c @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <unistd.h> @@ -35,13 +31,13 @@ #define X(s) (1 << (s)) -const struct osmo_tdef_state_timeout tbf_dl_ass_fsm_timeouts[32] = { +static const struct osmo_tdef_state_timeout tbf_dl_ass_fsm_timeouts[32] = { [TBF_DL_ASS_NONE] = {}, [TBF_DL_ASS_SEND_ASS] = {}, [TBF_DL_ASS_WAIT_ACK] = {}, }; -const struct value_string tbf_dl_ass_fsm_event_names[] = { +static const struct value_string tbf_dl_ass_fsm_event_names[] = { { TBF_DL_ASS_EV_SCHED_ASS, "SCHED_ASS" }, { TBF_DL_ASS_EV_CREATE_RLCMAC_MSG, "CREATE_RLCMAC_MSG" }, { TBF_DL_ASS_EV_RX_ASS_CTRL_ACK, "RX_ASS_CTRL_ACK" }, @@ -49,7 +45,16 @@ const struct value_string tbf_dl_ass_fsm_event_names[] = { { 0, NULL } }; -struct msgb *create_packet_dl_assign(const struct tbf_dl_ass_fsm_ctx *ctx, +/* Transition to a state, using the T timer defined in tbf_dl_ass_fsm_timeouts. + * The actual timeout value is in turn obtained from conn->T_defs. + * Assumes local variable fi exists. */ +#define tbf_dl_ass_fsm_state_chg(fi, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \ + tbf_dl_ass_fsm_timeouts, \ + the_pcu->T_defs, \ + -1) + +static struct msgb *create_packet_dl_assign(const struct tbf_dl_ass_fsm_ctx *ctx, const struct tbf_dl_ass_ev_create_rlcmac_msg_ctx *d) { struct msgb *msg; @@ -63,9 +68,9 @@ struct msgb *create_packet_dl_assign(const struct tbf_dl_ass_fsm_ctx *ctx, bool old_tfi_is_valid = tbf_is_tfi_assigned(ctx->tbf); /* We only use this function in control TS (PACCH) so that MS can always answer the poll */ - OSMO_ASSERT(tbf_is_control_ts(ctx->tbf, d->ts)); + OSMO_ASSERT(tbf_is_control_ts(ctx->tbf, d->pdch)); - rc = tbf_check_polling(ctx->tbf, d->fn, d->ts, &new_poll_fn, &rrbp); + rc = tbf_check_polling(ctx->tbf, d->pdch, d->fn, &new_poll_fn, &rrbp); if (rc < 0) return NULL; @@ -77,9 +82,6 @@ struct msgb *create_packet_dl_assign(const struct tbf_dl_ass_fsm_ctx *ctx, return NULL; } - if (new_dl_tbf == as_dl_tbf(ctx->tbf)) - LOGPTBF(ctx->tbf, LOGL_DEBUG, "New and old TBF are the same.\n"); - if (old_tfi_is_valid && ms_tlli(ms) == GSM_RESERVED_TMSI) { LOGPTBF(ctx->tbf, LOGL_ERROR, "The old TFI is not assigned and there is no TLLI. New TBF %s\n", @@ -99,7 +101,12 @@ struct msgb *create_packet_dl_assign(const struct tbf_dl_ass_fsm_ctx *ctx, }; bitvec_unhex(&bv, DUMMY_VEC); - LOGPTBF((struct gprs_rlcmac_tbf *)new_dl_tbf, LOGL_INFO, "start Packet Downlink Assignment (PACCH)\n"); + if (ctx->tbf != (struct gprs_rlcmac_tbf *)new_dl_tbf) + LOGPTBF(ctx->tbf, LOGL_INFO, "start Packet Downlink Assignment (PACCH) for %s\n", + tbf_name((const struct gprs_rlcmac_tbf *)new_dl_tbf)); + else + LOGPTBF(ctx->tbf, LOGL_INFO, "start Packet Downlink Assignment (PACCH)\n"); + mac_control_block = (RlcMacDownlink_t *)talloc_zero(ctx->tbf, RlcMacDownlink_t); write_packet_downlink_assignment(mac_control_block, old_tfi_is_valid, tbf_tfi(ctx->tbf), (tbf_direction(ctx->tbf) == GPRS_RLCMAC_DL_TBF), @@ -115,9 +122,9 @@ struct msgb *create_packet_dl_assign(const struct tbf_dl_ass_fsm_ctx *ctx, LOGP(DTBF, LOGL_DEBUG, "------------------------- TX : Packet Downlink Assignment -------------------------\n"); bts_do_rate_ctr_inc(ms->bts, CTR_PKT_DL_ASSIGNMENT); - tbf_set_polling(ctx->tbf, new_poll_fn, d->ts, PDCH_ULC_POLL_DL_ASS); - LOGPTBFDL(ctx->tbf, LOGL_INFO, "Scheduled DL Assignment polling on PACCH (FN=%d, TS=%d)\n", - new_poll_fn, d->ts); + tbf_set_polling(ctx->tbf, d->pdch, new_poll_fn, PDCH_ULC_POLL_DL_ASS); + LOGPTBF(ctx->tbf, LOGL_INFO, "Scheduled DL Assignment polling on PACCH (FN=%d, TS=%d)\n", + new_poll_fn, d->pdch->ts_no); talloc_free(mac_control_block); return msg; @@ -183,11 +190,7 @@ static void st_wait_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) static int tbf_dl_ass_fsm_timer_cb(struct osmo_fsm_inst *fi) { - struct tbf_dl_ass_fsm_ctx *ctx = (struct tbf_dl_ass_fsm_ctx *)fi->priv; switch (fi->T) { - case -2000: - tbf_free(ctx->tbf); - break; default: OSMO_ASSERT(0); } @@ -206,7 +209,9 @@ static struct osmo_fsm_state tbf_dl_ass_fsm_states[] = { }, [TBF_DL_ASS_SEND_ASS] = { .in_event_mask = X(TBF_DL_ASS_EV_CREATE_RLCMAC_MSG), - .out_state_mask = X(TBF_DL_ASS_WAIT_ACK), + .out_state_mask = + X(TBF_DL_ASS_WAIT_ACK) | + X(TBF_DL_ASS_NONE), .name = "SEND_ASS", .action = st_send_ass, }, @@ -237,12 +242,14 @@ static __attribute__((constructor)) void tbf_dl_ass_fsm_init(void) } -struct msgb *tbf_dl_ass_create_rlcmac_msg(const struct gprs_rlcmac_tbf* tbf, uint32_t fn, uint8_t ts) +struct msgb *tbf_dl_ass_create_rlcmac_msg(const struct gprs_rlcmac_tbf *tbf, + const struct gprs_rlcmac_pdch *pdch, + uint32_t fn) { int rc; struct tbf_dl_ass_ev_create_rlcmac_msg_ctx data_ctx = { + .pdch = pdch, .fn = fn, - .ts = ts, .msg = NULL, }; @@ -252,9 +259,14 @@ struct msgb *tbf_dl_ass_create_rlcmac_msg(const struct gprs_rlcmac_tbf* tbf, uin return data_ctx.msg; } -bool tbf_dl_ass_rts(const struct gprs_rlcmac_tbf *tbf) +bool tbf_dl_ass_rts(const struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch) { - struct osmo_fsm_inst *fi = tbf_dl_ass_fi(tbf); + struct osmo_fsm_inst *fi; + + if (!tbf_is_control_ts(tbf, pdch)) + return false; + + fi = tbf_dl_ass_fi(tbf); if (fi->state != TBF_DL_ASS_SEND_ASS) return false; diff --git a/src/tbf_dl_ass_fsm.h b/src/tbf_dl_ass_fsm.h index 94c9062d..cb7be8d3 100644 --- a/src/tbf_dl_ass_fsm.h +++ b/src/tbf_dl_ass_fsm.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -25,6 +21,7 @@ #include <gprs_pcu.h> struct gprs_rlcmac_tbf; +struct gprs_rlcmac_pdch; enum tbf_dl_ass_fsm_event { TBF_DL_ASS_EV_SCHED_ASS, /* Tx Uplink Assignment is pending */ @@ -44,26 +41,18 @@ struct tbf_dl_ass_fsm_ctx { struct gprs_rlcmac_tbf* tbf; /* back pointer */ }; -extern const struct osmo_tdef_state_timeout tbf_dl_ass_fsm_timeouts[32]; -/* Transition to a state, using the T timer defined in tbf_dl_ass_fsm_timeouts. - * The actual timeout value is in turn obtained from conn->T_defs. - * Assumes local variable fi exists. */ -#define tbf_dl_ass_fsm_state_chg(fi, NEXT_STATE) \ - osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \ - tbf_dl_ass_fsm_timeouts, \ - the_pcu->T_defs, \ - -1) - extern struct osmo_fsm tbf_dl_ass_fsm; /* passed as data in TBF_DL_ASS_EV_CREATE_RLCMAC_MSG */ struct tbf_dl_ass_ev_create_rlcmac_msg_ctx { + const struct gprs_rlcmac_pdch *pdch; /* TS where the created DL ctrl block is to be sent */ uint32_t fn; /* FN where the created DL ctrl block is to be sent */ - uint8_t ts; /* TS where the created DL ctrl block is to be sent */ struct msgb *msg; /* to be filled by FSM during event processing */ }; -struct msgb *tbf_dl_ass_create_rlcmac_msg(const struct gprs_rlcmac_tbf* tbf, uint32_t fn, uint8_t ts); -bool tbf_dl_ass_rts(const struct gprs_rlcmac_tbf* tbf); +struct msgb *tbf_dl_ass_create_rlcmac_msg(const struct gprs_rlcmac_tbf *tbf, + const struct gprs_rlcmac_pdch *pdch, + uint32_t fn); +bool tbf_dl_ass_rts(const struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch); diff --git a/src/tbf_dl_fsm.c b/src/tbf_dl_fsm.c new file mode 100644 index 00000000..917c4b15 --- /dev/null +++ b/src/tbf_dl_fsm.c @@ -0,0 +1,529 @@ +/* tbf_dl_fsm.c + * + * Copyright (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <unistd.h> + +#include <talloc.h> + +#include <tbf_fsm.h> +#include <gprs_rlcmac.h> +#include <gprs_debug.h> +#include <gprs_ms.h> +#include <encoding.h> +#include <bts.h> + +#include <bts_pch_timer.h> + +#define X(s) (1 << (s)) + +static const struct osmo_tdef_state_timeout tbf_dl_fsm_timeouts[32] = { + [TBF_ST_NEW] = {}, + [TBF_ST_ASSIGN] = {}, + [TBF_ST_FLOW] = {}, + [TBF_ST_FINISHED] = {}, + [TBF_ST_WAIT_RELEASE] = { .T = 3192 }, + [TBF_ST_WAIT_REUSE_TFI] = { /* .T = 3193 set manually onenter subtracting T3192 */ }, + [TBF_ST_RELEASING] = { .T = 3195 }, +}; + +/* Transition to a state, using the T timer defined in tbf_dl_fsm_timeouts. + * The actual timeout value is in turn obtained from T_defs_bts. + */ +#define tbf_dl_fsm_state_chg(fi, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \ + tbf_dl_fsm_timeouts, \ + tbf_ms(((struct tbf_dl_fsm_ctx *)(fi->priv))->tbf)->bts->T_defs_bts, \ + -1) + +static void mod_ass_type(struct tbf_dl_fsm_ctx *ctx, uint8_t t, bool set) +{ + const char *ch = "UNKNOWN"; + bool prev_set = ctx->state_flags & (1 << t); + + switch (t) { + case GPRS_RLCMAC_FLAG_CCCH: + ch = "CCCH"; + break; + case GPRS_RLCMAC_FLAG_PACCH: + ch = "PACCH"; + break; + default: + OSMO_ASSERT(0); + } + + LOGPTBFDL(ctx->dl_tbf, LOGL_INFO, "%sset ass. type %s [prev CCCH:%u, PACCH:%u]\n", + set ? "" : "un", ch, + !!(ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)), + !!(ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH))); + + if (set && prev_set) { + LOGPTBFDL(ctx->dl_tbf, LOGL_ERROR, + "Attempted to set ass. type %s which is already set\n", ch); + return; + } + + if (!set && !prev_set) + return; + + if (set) + ctx->state_flags |= (1 << t); + else + ctx->state_flags &= ~(1 << t); +} + + +static void st_new(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct tbf_dl_fsm_ctx *ctx = (struct tbf_dl_fsm_ctx *)fi->priv; + switch (event) { + case TBF_EV_ASSIGN_ADD_CCCH: + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_CCCH, true); + tbf_dl_fsm_state_chg(fi, TBF_ST_ASSIGN); + break; + case TBF_EV_ASSIGN_ADD_PACCH: + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_PACCH, true); + tbf_dl_fsm_state_chg(fi, TBF_ST_ASSIGN); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_assign_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct tbf_dl_fsm_ctx *ctx = (struct tbf_dl_fsm_ctx *)fi->priv; + struct GprsMs *ms = tbf_ms(ctx->tbf); + unsigned long val; + unsigned int sec, micro; + + /* If assignment for this TBF is happening on PACCH, that means the + * actual Assignment procedure (tx/rx) is happening on another TBF (eg + * Ul TBF vs DL TBF). Hence we add a security timer here to free it in + * case the other TBF doesn't succeed in informing (assigning) the MS + * about this TBF, or simply because the scheduler takes too long to + * schedule it. This timer can probably be dropped once we make the + * other TBF always signal us assignment failure (we already get + * assignment success through TBF_EV_ASSIGN_ACK_PACCH) */ + if (ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH)) { + fi->T = -2001; + val = osmo_tdef_get(the_pcu->T_defs, fi->T, OSMO_TDEF_MS, -1); + sec = val / 1000; + micro = (val % 1000) * 1000; + LOGPTBFDL(ctx->dl_tbf, LOGL_DEBUG, + "Starting timer X2001 [assignment (PACCH)] with %u sec. %u microsec\n", + sec, micro); + osmo_timer_schedule(&fi->timer, sec, micro); + } else { + /* GPRS_RLCMAC_FLAG_CCCH is set, so here we submit a DL Ass + * through PCUIF on CCCH */ + OSMO_ASSERT(ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)); + /* Send CCCH (PCH) Immediate Assignment over PCUIF: */ + bts_snd_dl_ass(ms->bts, ctx->dl_tbf); + } +} + +static void st_assign(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct tbf_dl_fsm_ctx *ctx = (struct tbf_dl_fsm_ctx *)fi->priv; + struct GprsMs *ms; + unsigned long val; + unsigned int sec, micro; + + switch (event) { + case TBF_EV_ASSIGN_ADD_CCCH: + /* Note: This code path is not really used nowadays, since ADD_CCCH is + * only dispatched during dl_tbf allocation (st=NEW) */ + ms = tbf_ms(ctx->tbf); + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_CCCH, true); + /* Re-send CCCH (PCH) Immediate Assignment over PCUIF: */ + bts_snd_dl_ass(ms->bts, ctx->dl_tbf); + break; + case TBF_EV_ASSIGN_ADD_PACCH: + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_PACCH, true); + break; + case TBF_EV_ASSIGN_ACK_PACCH: + tbf_assign_control_ts(ctx->tbf); + if (ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)) { + /* We now know that the PACCH really existed */ + LOGPTBFDL(ctx->dl_tbf, LOGL_INFO, + "The TBF has been confirmed on the PACCH, " + "changed type from CCCH to PACCH\n"); + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_CCCH, false); + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_PACCH, true); + } + tbf_dl_fsm_state_chg(fi, TBF_ST_FLOW); + break; + case TBF_EV_ASSIGN_PCUIF_CNF: + /* BTS informs us it sent Imm Ass for DL TBF over CCCH. We now + * have to wait for X2002 to trigger (meaning MS is already + * listening on PDCH) in order to move to FLOW state and start + * transmitting data to it. When X2002 triggers (see cb timer + * end of the file) it will send TBF_EV_ASSIGN_READY_CCCH back + * to us here. + */ + if (!(ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH))) { + /* This can happen if we initiated a CCCH DlAss from an + * older TBF object (same TLLI) towards BTS, and the DL-TBF + * was recreated and is now trying to be assigned through + * PACCH. + */ + LOGPTBFDL(ctx->dl_tbf, LOGL_INFO, + "Ignoring event ASSIGN_PCUIF_CNF from BTS " + "(CCCH was not requested on current assignment)\n"); + break; + } + fi->T = -2002; + val = osmo_tdef_get(the_pcu->T_defs, fi->T, OSMO_TDEF_MS, -1); + sec = val / 1000; + micro = (val % 1000) * 1000; + LOGPTBFDL(ctx->dl_tbf, LOGL_DEBUG, + "Starting timer X2002 [assignment (PCH)] with %u sec. %u microsec\n", + sec, micro); + osmo_timer_schedule(&fi->timer, sec, micro); + break; + case TBF_EV_ASSIGN_READY_CCCH: + /* change state to FLOW, so scheduler will start transmission */ + tbf_dl_fsm_state_chg(fi, TBF_ST_FLOW); + break; + case TBF_EV_MAX_N3105: + /* We are going to release, so abort any Pkt Ul Ass pending to be scheduled: */ + osmo_fsm_inst_dispatch(tbf_ul_ass_fi(ctx->tbf), TBF_UL_ASS_EV_ABORT, NULL); + tbf_dl_fsm_state_chg(fi, TBF_ST_RELEASING); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_flow(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct tbf_dl_fsm_ctx *ctx = (struct tbf_dl_fsm_ctx *)fi->priv; + + switch (event) { + case TBF_EV_ASSIGN_PCUIF_CNF: + if (!(ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH))) { + /* This can happen if we initiated a CCCH DlAss from an + * older TBF object (same TLLI) towards BTS, and the DL-TBF + * was recreated (this one) and was successfully assigned over PACCH. + * This is usually the case if MS requests 2phase access + * to get an UL TBF while we were waiting for a DL TBF + * assignment for that same MS over PCH. + */ + LOGPTBFDL(ctx->dl_tbf, LOGL_INFO, + "Ignoring event ASSIGN_PCUIF_CNF from BTS " + "(CCCH was not requested on current assignment)\n"); + } + break; + case TBF_EV_DL_ACKNACK_MISS: + /* DL TBF: we missed a DL ACK/NACK. If we started assignment + * over CCCH and never received any DL ACK/NACK yet, it means we + * don't even know if the MS successfully received the Imm Ass on + * CCCH and hence is listening on PDCH. Let's better refrain + * from continuing and start assignment on CCCH again */ + if ((ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)) && + !dl_tbf_first_dl_ack_rcvd(ctx->dl_tbf)) { + LOGPTBFDL(ctx->dl_tbf, LOGL_DEBUG, "Retransmit ImmAss[PktDlAss] on PCH\n"); + tbf_dl_fsm_state_chg(fi, TBF_ST_ASSIGN); + } + break; + case TBF_EV_LAST_DL_DATA_SENT: + /* All data has been sent or received, change state to FINISHED */ + tbf_dl_fsm_state_chg(fi, TBF_ST_FINISHED); + break; + case TBF_EV_MAX_N3105: + tbf_dl_fsm_state_chg(fi, TBF_ST_RELEASING); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_finished(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case TBF_EV_DL_ACKNACK_MISS: + break; + case TBF_EV_FINAL_ACK_RECVD: + /* We received Final Ack (DL ACK/NACK) from MS. move to + * WAIT_RELEASE, where MS stays monitoring PDCH over T3192 span, + * where we can use this DL TBF to assign a new one in case we + * receive more DL data to Tx */ + tbf_dl_fsm_state_chg(fi, TBF_ST_WAIT_RELEASE); + break; + case TBF_EV_MAX_N3105: + tbf_dl_fsm_state_chg(fi, TBF_ST_RELEASING); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_wait_release_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct tbf_dl_fsm_ctx *ctx = (struct tbf_dl_fsm_ctx *)fi->priv; + struct GprsMs *ms = tbf_ms(ctx->tbf); + + /* This state was entered because FinalACK was received; now T3192 is + * running on the MS and has also been armed by this FSM. + * During that time, it is possible to reach the MS over PACCH to assign + * new DL TBF. + * Upon T3192 expiration, FSM will transition to TBF_ST_WAIT_REUSE_TFI + * for some more time (T3193 - T3192) until internally freeing the TBF + * object, at which time the resources can be reused. + */ + + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_CCCH, false); + + /* check for LLC PDU in the LLC Queue */ + if (llc_queue_size(ms_llc_queue(ms)) > 0) { + /* we have more data so we will re-use this tbf */ + ms_new_dl_tbf_assigned_on_pacch(ms, ctx->tbf); + } +} + +static void st_wait_release(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case TBF_EV_FINAL_ACK_RECVD: + /* ignore, duplicate ACK, we already know about since we left ST_FINISHED */ + break; + case TBF_EV_DL_ACKNACK_MISS: + /* ignore, miss for retransmitted ACK, but a previous one was + * already ACKED since we left ST_FINISHED. This happens due to + * fn-advance scheduling several DL blocks in advance. */ + break; + case TBF_EV_MAX_N3105: + /* Triggered potentially by a poll timeout of PKT UL/DL TBF ASS. + * Reached N3105, the MS is not responding, so stop attempting + * using the TBF for DL assignment and change to ST_RELEASING in + * order to simply wait until resources can be reused (see + * st_releasing_on_enter()). */ + tbf_dl_fsm_state_chg(fi, TBF_ST_RELEASING); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_wait_reuse_tfi_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct tbf_dl_fsm_ctx *ctx = (struct tbf_dl_fsm_ctx *)fi->priv; + struct GprsMs *ms = tbf_ms(ctx->tbf); + unsigned long t3192, t3193, res; + + /* T3192 has expired, so the MS is not listening on that PACCH anymore. + * Still, wait until remaining of T3193 expiration (>T3192) to internally + * free the TBF, at which point the TFI and other allocated resources + * will be freed and can then be reused. + */ + + t3192 = osmo_tdef_get(ms->bts->T_defs_bts, 3192, OSMO_TDEF_MS, -1); + t3193 = osmo_tdef_get(ms->bts->T_defs_bts, 3193, OSMO_TDEF_MS, -1); + /* As per spec T3193 shall be greater than T3192, but let's be safe against wrong configs: */ + res = (t3193 >= t3192) ? (t3193 - t3192) : 0; + fi->T = 3193; + LOGPTBF(ctx->tbf, LOGL_DEBUG, "Waiting %lu sec. %lu microsec (T3193 - T3192) [REUSE TFI]\n", + res / 1000, (res % 1000) * 1000); + osmo_timer_schedule(&fi->timer, res / 1000, (res % 1000) * 1000); +} + +static void st_wait_reuse_tfi(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + /* Simply wait for T3193 timeout, it will tbf_free() */ + switch (event) { + case TBF_EV_FINAL_ACK_RECVD: + /* ignore, duplicate ACK, we already know about since we left ST_FINISHED */ + break; + case TBF_EV_DL_ACKNACK_MISS: + /* ignore, miss for retransmitted ACK, but a previous one was + * already ACKED since we left ST_FINISHED. This happens due to + * fn-advance scheduling several DL blocks in advance. */ + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_releasing_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + /* T3195 has been set entering this state: Wait for reuse of TFI(s) when + * there is no response from the MS (radio failure or cell change) for this + * TBF/MBMS radio bearer. Upon timeout, the timer_cb does tbf_free(). + */ +} + +static void st_releasing(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case TBF_EV_DL_ACKNACK_MISS: + /* Ignore, we don't care about missed DL ACK/NACK poll timeouts + * anymore, we are already releasing the TBF */ + break; + default: + OSMO_ASSERT(0); + } +} + +static void handle_timeout_X2002(struct osmo_fsm_inst *fi) +{ + struct tbf_dl_fsm_ctx *ctx = (struct tbf_dl_fsm_ctx *)fi->priv; + int rc; + + if (fi->state != TBF_ST_ASSIGN) { + LOGPTBFDL(ctx->dl_tbf, LOGL_NOTICE, "Continue flow after IMM.ASS confirm\n"); + return; + } + + /* state TBF_ST_ASSIGN: */ + tbf_assign_control_ts(ctx->tbf); + + if (!tbf_can_upgrade_to_multislot(ctx->tbf)) { + /* change state to FLOW, so scheduler will start transmission */ + osmo_fsm_inst_dispatch(fi, TBF_EV_ASSIGN_READY_CCCH, NULL); + return; + } + + /* This tbf can be upgraded to use multiple DL timeslots and now that there is already + * one slot assigned send another DL assignment via PDCH. + */ + + /* Reset state flags */ + ctx->state_flags = 0x00; + + rc = dl_tbf_upgrade_to_multislot(ctx->dl_tbf); + if (rc < 0) + tbf_free(ctx->tbf); +} + +static int tbf_dl_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct tbf_dl_fsm_ctx *ctx = (struct tbf_dl_fsm_ctx *)fi->priv; + switch (fi->T) { + case -2002: + handle_timeout_X2002(fi); + break; + case 3192: + tbf_dl_fsm_state_chg(fi, TBF_ST_WAIT_REUSE_TFI); + break; + case -2001: + LOGPTBFDL(ctx->dl_tbf, LOGL_NOTICE, "releasing due to PACCH assignment timeout.\n"); + /* fall-through */ + case 3193: + case 3195: + tbf_free(ctx->tbf); + break; + default: + OSMO_ASSERT(0); + } + return 0; +} + +static struct osmo_fsm_state tbf_dl_fsm_states[] = { + [TBF_ST_NEW] = { + .in_event_mask = + X(TBF_EV_ASSIGN_ADD_CCCH) | + X(TBF_EV_ASSIGN_ADD_PACCH), + .out_state_mask = + X(TBF_ST_ASSIGN), + .name = "NEW", + .action = st_new, + }, + [TBF_ST_ASSIGN] = { + .in_event_mask = + X(TBF_EV_ASSIGN_ADD_CCCH) | + X(TBF_EV_ASSIGN_ADD_PACCH) | + X(TBF_EV_ASSIGN_ACK_PACCH) | + X(TBF_EV_ASSIGN_PCUIF_CNF) | + X(TBF_EV_ASSIGN_READY_CCCH) | + X(TBF_EV_MAX_N3105), + .out_state_mask = + X(TBF_ST_FLOW) | + X(TBF_ST_FINISHED) | + X(TBF_ST_RELEASING), + .name = "ASSIGN", + .action = st_assign, + .onenter = st_assign_on_enter, + }, + [TBF_ST_FLOW] = { + .in_event_mask = + X(TBF_EV_ASSIGN_PCUIF_CNF) | + X(TBF_EV_DL_ACKNACK_MISS) | + X(TBF_EV_LAST_DL_DATA_SENT) | + X(TBF_EV_MAX_N3105), + .out_state_mask = + X(TBF_ST_ASSIGN) | + X(TBF_ST_FINISHED) | + X(TBF_ST_RELEASING), + .name = "FLOW", + .action = st_flow, + }, + [TBF_ST_FINISHED] = { + .in_event_mask = + X(TBF_EV_DL_ACKNACK_MISS) | + X(TBF_EV_FINAL_ACK_RECVD) | + X(TBF_EV_MAX_N3105), + .out_state_mask = + X(TBF_ST_WAIT_RELEASE) | + X(TBF_ST_RELEASING), + .name = "FINISHED", + .action = st_finished, + }, + [TBF_ST_WAIT_RELEASE] = { + .in_event_mask = + X(TBF_EV_DL_ACKNACK_MISS) | + X(TBF_EV_FINAL_ACK_RECVD) | + X(TBF_EV_MAX_N3105), + .out_state_mask = + X(TBF_ST_WAIT_REUSE_TFI) | + X(TBF_ST_RELEASING), + .name = "WAIT_RELEASE", + .action = st_wait_release, + .onenter = st_wait_release_on_enter, + }, + [TBF_ST_WAIT_REUSE_TFI] = { + .in_event_mask = + X(TBF_EV_DL_ACKNACK_MISS) | + X(TBF_EV_FINAL_ACK_RECVD), + .out_state_mask = + X(TBF_ST_RELEASING), + .name = "WAIT_REUSE_TFI", + .action = st_wait_reuse_tfi, + .onenter = st_wait_reuse_tfi_on_enter, + }, + [TBF_ST_RELEASING] = { + .in_event_mask = + X(TBF_EV_DL_ACKNACK_MISS), + .out_state_mask = + 0, + .name = "RELEASING", + .action = st_releasing, + .onenter = st_releasing_on_enter, + }, +}; + +struct osmo_fsm tbf_dl_fsm = { + .name = "DL_TBF", + .states = tbf_dl_fsm_states, + .num_states = ARRAY_SIZE(tbf_dl_fsm_states), + .timer_cb = tbf_dl_fsm_timer_cb, + .log_subsys = DTBFDL, + .event_names = tbf_fsm_event_names, +}; + +static __attribute__((constructor)) void tbf_dl_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&tbf_dl_fsm) == 0); +} diff --git a/src/tbf_fsm.c b/src/tbf_fsm.c index af2b34ef..f6dafc3c 100644 --- a/src/tbf_fsm.c +++ b/src/tbf_fsm.c @@ -1,6 +1,6 @@ /* tbf_fsm.c * - * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Copyright (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * Author: Pau Espin Pedrol <pespin@sysmocom.de> * * This program is free software; you can redistribute it and/or @@ -12,33 +12,14 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include <unistd.h> - -#include <talloc.h> +#include <osmocom/core/utils.h> #include <tbf_fsm.h> -#include <gprs_rlcmac.h> -#include <gprs_debug.h> -#include <gprs_ms.h> -#include <encoding.h> -#include <bts.h> -#define X(s) (1 << (s)) - -const struct osmo_tdef_state_timeout tbf_fsm_timeouts[32] = { - [TBF_ST_NEW] = {}, - [TBF_ST_ASSIGN] = { }, - [TBF_ST_FLOW] = { }, - [TBF_ST_FINISHED] = {}, - [TBF_ST_WAIT_RELEASE] = {}, - [TBF_ST_RELEASING] = {}, -}; +/* Note: This file contains shared code for UL/DL TBF FSM. See tbf_dl_fsm.c and + * tbf_ul_fsm.c for the actual implementations of the FSM */ const struct value_string tbf_fsm_event_names[] = { { TBF_EV_ASSIGN_ADD_CCCH, "ASSIGN_ADD_CCCH" }, @@ -46,6 +27,8 @@ const struct value_string tbf_fsm_event_names[] = { { TBF_EV_ASSIGN_ACK_PACCH, "ASSIGN_ACK_PACCH" }, { TBF_EV_ASSIGN_READY_CCCH, "ASSIGN_READY_CCCH" }, { TBF_EV_ASSIGN_PCUIF_CNF, "ASSIGN_PCUIF_CNF" }, + { TBF_EV_FIRST_UL_DATA_RECVD, "FIRST_UL_DATA_RECVD" }, + { TBF_EV_CONTENTION_RESOLUTION_MS_SUCCESS, "CONTENTION_RESOLUTION_MS_SUCCESS" }, { TBF_EV_DL_ACKNACK_MISS, "DL_ACKNACK_MISS" }, { TBF_EV_LAST_DL_DATA_SENT, "LAST_DL_DATA_SENT" }, { TBF_EV_LAST_UL_DATA_RECVD, "LAST_UL_DATA_RECVD" }, @@ -56,440 +39,3 @@ const struct value_string tbf_fsm_event_names[] = { { TBF_EV_MAX_N3105 , "MAX_N3105" }, { 0, NULL } }; - -static void mod_ass_type(struct tbf_fsm_ctx *ctx, uint8_t t, bool set) -{ - const char *ch = "UNKNOWN"; - bool prev_set = ctx->state_flags & (1 << t); - - switch (t) { - case GPRS_RLCMAC_FLAG_CCCH: - ch = "CCCH"; - break; - case GPRS_RLCMAC_FLAG_PACCH: - ch = "PACCH"; - break; - default: - LOGPTBF(ctx->tbf, LOGL_ERROR, - "attempted to %sset unexpected ass. type %d - FIXME!\n", - set ? "" : "un", t); - return; - } - - if (set && prev_set) { - LOGPTBF(ctx->tbf, LOGL_ERROR, - "attempted to set ass. type %s which is already set.\n", ch); - } else if (!set && !prev_set) { - return; - } - - LOGPTBF(ctx->tbf, LOGL_INFO, "%sset ass. type %s [prev CCCH:%u, PACCH:%u]\n", - set ? "" : "un", ch, - !!(ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)), - !!(ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH))); - - if (set) { - ctx->state_flags |= (1 << t); - } else { - ctx->state_flags &= GPRS_RLCMAC_FLAG_TO_MASK; /* keep to flags */ - ctx->state_flags &= ~(1 << t); - } -} - - -static void st_new(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct tbf_fsm_ctx *ctx = (struct tbf_fsm_ctx *)fi->priv; - switch (event) { - case TBF_EV_ASSIGN_ADD_CCCH: - mod_ass_type(ctx, GPRS_RLCMAC_FLAG_CCCH, true); - tbf_fsm_state_chg(fi, tbf_direction(ctx->tbf) == GPRS_RLCMAC_DL_TBF ? - TBF_ST_ASSIGN : TBF_ST_FLOW); - break; - case TBF_EV_ASSIGN_ADD_PACCH: - mod_ass_type(ctx, GPRS_RLCMAC_FLAG_PACCH, true); - tbf_fsm_state_chg(fi, TBF_ST_ASSIGN); - break; - default: - OSMO_ASSERT(0); - } -} - -static void st_assign_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) -{ - struct tbf_fsm_ctx *ctx = (struct tbf_fsm_ctx *)fi->priv; - unsigned long val; - unsigned int sec, micro; - - /* If assignment for this TBF is happening on PACCH, that means the - * actual Assignment procedure (tx/rx) is happening on another TBF (eg - * Ul TBF vs DL TBF). Hence we add a security timer here to free it in - * case the other TBF doesn't succeed in informing (assigning) the MS - * about this TBF, or simply because the scheduler takes too long to - * schedule it. This timer can probably be dropped once we make the - * other TBF always signal us assignment failure (we already get - * assignment success through TBF_EV_ASSIGN_ACK_PACCH) */ - if (ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH)) { - fi->T = -2001; - val = osmo_tdef_get(the_pcu->T_defs, fi->T, OSMO_TDEF_MS, -1); - sec = val / 1000; - micro = (val % 1000) * 1000; - LOGPTBF(ctx->tbf, LOGL_DEBUG, - "Starting timer X2001 [assignment (PACCH)] with %u sec. %u microsec\n", - sec, micro); - osmo_timer_schedule(&fi->timer, sec, micro); - } else if (tbf_direction(ctx->tbf) == GPRS_RLCMAC_DL_TBF) { - /* GPRS_RLCMAC_FLAG_CCCH is set, so here we submitted an DL Ass through PCUIF on CCCH */ - } -} - -static void st_assign(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct tbf_fsm_ctx *ctx = (struct tbf_fsm_ctx *)fi->priv; - unsigned long val; - unsigned int sec, micro; - - switch (event) { - case TBF_EV_ASSIGN_ADD_CCCH: - mod_ass_type(ctx, GPRS_RLCMAC_FLAG_CCCH, true); - break; - case TBF_EV_ASSIGN_ADD_PACCH: - mod_ass_type(ctx, GPRS_RLCMAC_FLAG_PACCH, true); - break; - case TBF_EV_ASSIGN_ACK_PACCH: - tbf_assign_control_ts(ctx->tbf); - if (ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)) { - /* We now know that the PACCH really existed */ - LOGPTBF(ctx->tbf, LOGL_INFO, - "The TBF has been confirmed on the PACCH, " - "changed type from CCCH to PACCH\n"); - mod_ass_type(ctx, GPRS_RLCMAC_FLAG_CCCH, false); - mod_ass_type(ctx, GPRS_RLCMAC_FLAG_PACCH, true); - } - tbf_fsm_state_chg(fi, TBF_ST_FLOW); - break; - case TBF_EV_ASSIGN_PCUIF_CNF: - /* BTS informs us it sent Imm Ass for DL TBF over CCCH. We now - * have to wait for X2002 to trigger (meaning MS is already - * listening on PDCH) in order to move to FLOW state and start - * transmitting data to it. When X2002 triggers (see cb timer - * end of the file) it will send TBF_EV_ASSIGN_READY_CCCH back - * to us here. */ - OSMO_ASSERT(tbf_direction(ctx->tbf) == GPRS_RLCMAC_DL_TBF); - fi->T = -2002; - val = osmo_tdef_get(the_pcu->T_defs, fi->T, OSMO_TDEF_MS, -1); - sec = val / 1000; - micro = (val % 1000) * 1000; - LOGPTBF(ctx->tbf, LOGL_DEBUG, - "Starting timer X2002 [assignment (AGCH)] with %u sec. %u microsec\n", - sec, micro); - osmo_timer_schedule(&fi->timer, sec, micro); - break; - case TBF_EV_ASSIGN_READY_CCCH: - /* change state to FLOW, so scheduler will start transmission */ - tbf_fsm_state_chg(fi, TBF_ST_FLOW); - break; - case TBF_EV_MAX_N3105: - /* We are going to release, so abort any Pkt Ul Ass pending to be scheduled: */ - osmo_fsm_inst_dispatch(tbf_ul_ass_fi(ctx->tbf), TBF_UL_ASS_EV_ABORT, NULL); - ctx->T_release = 3195; - tbf_fsm_state_chg(fi, TBF_ST_RELEASING); - break; - default: - OSMO_ASSERT(0); - } -} - -static void st_flow(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct tbf_fsm_ctx *ctx = (struct tbf_fsm_ctx *)fi->priv; - - switch (event) { - case TBF_EV_DL_ACKNACK_MISS: - /* DL TBF: we missed a DL ACK/NACK. If we started assignment - * over CCCH and never received any DL ACK/NACK yet, it means we - * don't even know if the MS successfuly received the Imm Ass on - * CCCH and hence is listening on PDCH. Let's better refrain - * from continuing and start assignment on CCCH again */ - if ((ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)) - && !(ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_DL_ACK))) { - struct GprsMs *ms = tbf_ms(ctx->tbf); - const char *imsi = ms_imsi(ms); - uint16_t pgroup; - LOGPTBF(ctx->tbf, LOGL_DEBUG, "Re-send downlink assignment on PCH (IMSI=%s)\n", - imsi); - tbf_fsm_state_chg(fi, TBF_ST_ASSIGN); - /* send immediate assignment */ - if ((pgroup = imsi2paging_group(imsi)) > 999) - LOGPTBF(ctx->tbf, LOGL_ERROR, "IMSI to paging group failed! (%s)\n", imsi); - bts_snd_dl_ass(ms->bts, ctx->tbf, pgroup); - } - break; - case TBF_EV_LAST_DL_DATA_SENT: - case TBF_EV_LAST_UL_DATA_RECVD: - /* All data has been sent or received, change state to FINISHED */ - tbf_fsm_state_chg(fi, TBF_ST_FINISHED); - break; - case TBF_EV_FINAL_ACK_RECVD: - /* We received Final Ack (DL ACK/NACK) from MS. move to - WAIT_RELEASE, we wait there for release or re-use the TBF in - case we receive more DL data to tx */ - tbf_fsm_state_chg(fi, TBF_ST_WAIT_RELEASE); - break; - case TBF_EV_MAX_N3101: - ctx->T_release = 3169; - tbf_fsm_state_chg(fi, TBF_ST_RELEASING); - break; - case TBF_EV_MAX_N3105: - ctx->T_release = 3195; - tbf_fsm_state_chg(fi, TBF_ST_RELEASING); - break; - default: - OSMO_ASSERT(0); - } -} - -static void st_finished(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct tbf_fsm_ctx *ctx = (struct tbf_fsm_ctx *)fi->priv; - switch (event) { - case TBF_EV_DL_ACKNACK_MISS: - break; - case TBF_EV_FINAL_ACK_RECVD: - /* We received Final Ack (DL ACK/NACK) from MS. move to - WAIT_RELEASE, we wait there for release or re-use the TBF in - case we receive more DL data to tx */ - tbf_fsm_state_chg(fi, TBF_ST_WAIT_RELEASE); - break; - case TBF_EV_FINAL_UL_ACK_CONFIRMED: - /* UL TBF ACKed our transmitted UL ACK/NACK with final Ack - * Indicator set to '1' t. We can free the TBF right away. */ - tbf_free(ctx->tbf); - break; - case TBF_EV_MAX_N3103: - ctx->T_release = 3169; - tbf_fsm_state_chg(fi, TBF_ST_RELEASING); - break; - case TBF_EV_MAX_N3105: - ctx->T_release = 3195; - tbf_fsm_state_chg(fi, TBF_ST_RELEASING); - break; - default: - OSMO_ASSERT(0); - } -} - -static void st_wait_release_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) -{ - struct tbf_fsm_ctx *ctx = (struct tbf_fsm_ctx *)fi->priv; - unsigned long val_s, val_ms, val_us; - OSMO_ASSERT(tbf_direction(ctx->tbf) == GPRS_RLCMAC_DL_TBF); - - fi->T = 3193; - val_ms = osmo_tdef_get(tbf_ms(ctx->tbf)->bts->T_defs_bts, fi->T, OSMO_TDEF_MS, -1); - val_s = val_ms / 1000; - val_us = (val_ms % 1000) * 1000; - LOGPTBF(ctx->tbf, LOGL_DEBUG, "starting timer T%u with %lu sec. %lu microsec\n", - fi->T, val_s, val_us); - osmo_timer_schedule(&fi->timer, val_s, val_us); - - mod_ass_type(ctx, GPRS_RLCMAC_FLAG_CCCH, false); -} - -static void st_wait_release(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - struct tbf_fsm_ctx *ctx = (struct tbf_fsm_ctx *)fi->priv; - switch (event) { - case TBF_EV_FINAL_ACK_RECVD: - /* ignore, duplicate ACK, we already know about since we are in WAIT_RELEASE */ - break; - case TBF_EV_MAX_N3101: - ctx->T_release = 3169; - tbf_fsm_state_chg(fi, TBF_ST_RELEASING); - break; - case TBF_EV_MAX_N3105: - ctx->T_release = 3195; - tbf_fsm_state_chg(fi, TBF_ST_RELEASING); - break; - default: - OSMO_ASSERT(0); - } -} - -static void st_releasing_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) -{ - struct tbf_fsm_ctx *ctx = (struct tbf_fsm_ctx *)fi->priv; - unsigned long val; - - if (!ctx->T_release) - return; - - /* In general we should end up here with an assigned timer in ctx->T_release. Possible values are: - * T3195: Wait for reuse of TFI(s) when there is no response from the MS - * (radio failure or cell change) for this TBF/MBMS radio bearer. - * T3169: Wait for reuse of USF and TFI(s) after the MS uplink assignment for this TBF is invalid. - */ - val = osmo_tdef_get(tbf_ms(ctx->tbf)->bts->T_defs_bts, ctx->T_release, OSMO_TDEF_S, -1); - fi->T = ctx->T_release; - LOGPTBF(ctx->tbf, LOGL_DEBUG, "starting timer T%u with %lu sec. %u microsec\n", - ctx->T_release, val, 0); - osmo_timer_schedule(&fi->timer, val, 0); -} - -static void st_releasing(struct osmo_fsm_inst *fi, uint32_t event, void *data) -{ - switch (event) { - case TBF_EV_DL_ACKNACK_MISS: - /* Ignore, we don't care about missed DL ACK/NACK poll timeouts - * anymore, we are already releasing the TBF */ - break; - default: - OSMO_ASSERT(0); - } -} - -static void handle_timeout_X2002(struct tbf_fsm_ctx *ctx) -{ - struct gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(ctx->tbf); - - /* X2002 is used only for DL TBF */ - OSMO_ASSERT(dl_tbf); - - if (ctx->fi->state == TBF_ST_ASSIGN) { - tbf_assign_control_ts(ctx->tbf); - - if (!tbf_can_upgrade_to_multislot(ctx->tbf)) { - /* change state to FLOW, so scheduler - * will start transmission */ - osmo_fsm_inst_dispatch(ctx->fi, TBF_EV_ASSIGN_READY_CCCH, NULL); - return; - } - - /* This tbf can be upgraded to use multiple DL - * timeslots and now that there is already one - * slot assigned send another DL assignment via - * PDCH. */ - - /* keep to flags */ - ctx->state_flags &= GPRS_RLCMAC_FLAG_TO_MASK; - - tbf_update(ctx->tbf); - - tbf_dl_trigger_ass(dl_tbf, ctx->tbf); - } else - LOGPTBF(ctx->tbf, LOGL_NOTICE, "Continue flow after IMM.ASS confirm\n"); -} - -static int tbf_fsm_timer_cb(struct osmo_fsm_inst *fi) -{ - struct tbf_fsm_ctx *ctx = (struct tbf_fsm_ctx *)fi->priv; - switch (fi->T) { - case -2002: - handle_timeout_X2002(ctx); - break; - case -2001: - LOGPTBF(ctx->tbf, LOGL_NOTICE, "releasing due to PACCH assignment timeout.\n"); - /* fall-through */ - case 3169: - case 3193: - case 3195: - tbf_free(ctx->tbf); - break; - default: - OSMO_ASSERT(0); - } - return 0; -} - -static struct osmo_fsm_state tbf_fsm_states[] = { - [TBF_ST_NEW] = { - .in_event_mask = - X(TBF_EV_ASSIGN_ADD_CCCH) | - X(TBF_EV_ASSIGN_ADD_PACCH), - .out_state_mask = - X(TBF_ST_ASSIGN) | - X(TBF_ST_FLOW) | - X(TBF_ST_RELEASING), - .name = "NEW", - .action = st_new, - }, - [TBF_ST_ASSIGN] = { - .in_event_mask = - X(TBF_EV_ASSIGN_ADD_CCCH) | - X(TBF_EV_ASSIGN_ADD_PACCH) | - X(TBF_EV_ASSIGN_ACK_PACCH) | - X(TBF_EV_ASSIGN_PCUIF_CNF) | - X(TBF_EV_ASSIGN_READY_CCCH) | - X(TBF_EV_MAX_N3105), - .out_state_mask = - X(TBF_ST_FLOW) | - X(TBF_ST_FINISHED) | - X(TBF_ST_RELEASING), - .name = "ASSIGN", - .action = st_assign, - .onenter = st_assign_on_enter, - }, - [TBF_ST_FLOW] = { - .in_event_mask = - X(TBF_EV_DL_ACKNACK_MISS) | - X(TBF_EV_LAST_DL_DATA_SENT) | - X(TBF_EV_LAST_UL_DATA_RECVD) | - X(TBF_EV_FINAL_ACK_RECVD) | - X(TBF_EV_MAX_N3101) | - X(TBF_EV_MAX_N3105), - .out_state_mask = - X(TBF_ST_ASSIGN) | - X(TBF_ST_FINISHED) | - X(TBF_ST_WAIT_RELEASE) | - X(TBF_ST_RELEASING), - .name = "FLOW", - .action = st_flow, - }, - [TBF_ST_FINISHED] = { - .in_event_mask = - X(TBF_EV_DL_ACKNACK_MISS) | - X(TBF_EV_FINAL_ACK_RECVD) | - X(TBF_EV_FINAL_UL_ACK_CONFIRMED) | - X(TBF_EV_MAX_N3103) | - X(TBF_EV_MAX_N3105), - .out_state_mask = - X(TBF_ST_WAIT_RELEASE) | - X(TBF_ST_RELEASING), - .name = "FINISHED", - .action = st_finished, - }, - [TBF_ST_WAIT_RELEASE] = { - .in_event_mask = - X(TBF_EV_FINAL_ACK_RECVD) | - X(TBF_EV_MAX_N3101) | - X(TBF_EV_MAX_N3105), - .out_state_mask = - X(TBF_ST_RELEASING), - .name = "WAIT_RELEASE", - .action = st_wait_release, - .onenter = st_wait_release_on_enter, - }, - [TBF_ST_RELEASING] = { - .in_event_mask = - X(TBF_EV_DL_ACKNACK_MISS), - .out_state_mask = - 0, - .name = "RELEASING", - .action = st_releasing, - .onenter = st_releasing_on_enter, - }, -}; - -struct osmo_fsm tbf_fsm = { - .name = "TBF", - .states = tbf_fsm_states, - .num_states = ARRAY_SIZE(tbf_fsm_states), - .timer_cb = tbf_fsm_timer_cb, - .log_subsys = DTBF, - .event_names = tbf_fsm_event_names, -}; - -static __attribute__((constructor)) void tbf_fsm_init(void) -{ - OSMO_ASSERT(osmo_fsm_register(&tbf_fsm) == 0); -} diff --git a/src/tbf_fsm.h b/src/tbf_fsm.h index 614c319d..fd6ffa6f 100644 --- a/src/tbf_fsm.h +++ b/src/tbf_fsm.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -27,45 +23,55 @@ struct gprs_rlcmac_tbf; enum tbf_fsm_event { - TBF_EV_ASSIGN_ADD_CCCH, /* An assignment is sent over CCCH and confirmation from MS is pending */ + /* For both UL/DL TBF: */ + TBF_EV_ASSIGN_ADD_CCCH, /* An assignment is sent over CCCH and confirmation from MS is pending */ TBF_EV_ASSIGN_ADD_PACCH, /* An assignment is sent over PACCH and confirmation from MS is pending */ - TBF_EV_ASSIGN_ACK_PACCH, /* We received a CTRL ACK confirming assignment started on PACCH */ + TBF_EV_ASSIGN_ACK_PACCH, /* We received a CTRL ACK confirming assignment started on PACCH */ + TBF_EV_MAX_N3105, /* MAX N3105 (max poll timeout) reached */ + + /* Only for DL TBF: */ TBF_EV_ASSIGN_READY_CCCH, /* TBF Start Time timer triggered */ - TBF_EV_ASSIGN_PCUIF_CNF, /* Transmission of IMM.ASS for DL TBF to the MS confirmed by BTS over PCUIF */ - TBF_EV_DL_ACKNACK_MISS, /* DL TBF: We polled for DL ACK/NACK but we received none (POLL timeout) */ - TBF_EV_LAST_DL_DATA_SENT, /* DL TBF sends RLCMAC block containing last DL avilable data buffered */ - TBF_EV_LAST_UL_DATA_RECVD, /* UL TBF sends RLCMAC block containing last UL data (cv=0) */ + TBF_EV_ASSIGN_PCUIF_CNF, /* Transmission of IMM.ASS for to the MS confirmed by BTS over PCUIF */ + TBF_EV_DL_ACKNACK_MISS, /* We polled for DL ACK/NACK but we received none (POLL timeout) */ + TBF_EV_LAST_DL_DATA_SENT, /* Network sends RLCMAC block containing last DL avilable data buffered */ TBF_EV_FINAL_ACK_RECVD, /* DL ACK/NACK with FINAL_ACK=1 received from MS */ - TBF_EV_FINAL_UL_ACK_CONFIRMED, /* UL TBF: MS ACKs (CtrlAck or PktResReq) our UL ACK/NACK w/ FinalAckInd=1 */ - TBF_EV_MAX_N3101, /* MAX N3101 (max usf timeout) reached (UL TBF) */ - TBF_EV_MAX_N3103, /* MAX N3103 (max Pkt Ctrl Ack for last UL ACK/NACK timeout) reached (UL TBF) */ - TBF_EV_MAX_N3105, /* MAX N3105 (max poll timeout) reached (UL/DL TBF) */ + + /* Only for UL TBF: */ + TBF_EV_FIRST_UL_DATA_RECVD, /* Received first UL data from MS. Equals to Contention Resolution completed on the network side */ + TBF_EV_CONTENTION_RESOLUTION_MS_SUCCESS, /* Contention resolution success at the mobile station side (first UL_ACK_NACK confirming TLLI is received at the MS) */ + TBF_EV_LAST_UL_DATA_RECVD, /* MS ends RLCMAC block containing last UL data (cv=0) */ + TBF_EV_FINAL_UL_ACK_CONFIRMED, /* MS ACKs (CtrlAck or PktResReq) our UL ACK/NACK w/ FinalAckInd=1. data = (bool) MS requests establishment of a new UL-TBF. */ + TBF_EV_MAX_N3101, /* MAX N3101 (max usf timeout) reached */ + TBF_EV_MAX_N3103, /* MAX N3103 (max Pkt Ctrl Ack for last UL ACK/NACK timeout) reached */ }; +extern const struct value_string tbf_fsm_event_names[]; + enum tbf_fsm_states { TBF_ST_NEW = 0, /* new created TBF */ TBF_ST_ASSIGN, /* wait for downlink assignment */ TBF_ST_FLOW, /* RLC/MAC flow, resource needed */ TBF_ST_FINISHED, /* flow finished, wait for release */ - TBF_ST_WAIT_RELEASE,/* wait for release or restart of DL TBF */ - TBF_ST_RELEASING, /* releasing, wait to free TBI/USF */ + TBF_ST_WAIT_RELEASE, /* DL TBF: wait for release or restart */ + TBF_ST_WAIT_REUSE_TFI, /* DL TBF: wait to reuse TFI after last PKT ACK/NACK */ + TBF_ST_RELEASING, /* releasing, wait to free TFI/USF */ }; -struct tbf_fsm_ctx { - struct osmo_fsm_inst *fi; - struct gprs_rlcmac_tbf* tbf; /* back pointer */ +struct tbf_dl_fsm_ctx { + union { /* back pointer. union used to easily access superclass from ctx */ + struct gprs_rlcmac_tbf *tbf; + struct gprs_rlcmac_dl_tbf *dl_tbf; + }; uint32_t state_flags; - unsigned int T_release; /* Timer to be used to end release: T3169 or T3195 */ }; -extern const struct osmo_tdef_state_timeout tbf_fsm_timeouts[32]; -/* Transition to a state, using the T timer defined in tbf_fsm_timeouts. - * The actual timeout value is in turn obtained from conn->T_defs. - * Assumes local variable fi exists. */ -#define tbf_fsm_state_chg(fi, NEXT_STATE) \ - osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \ - tbf_fsm_timeouts, \ - the_pcu->T_defs, \ - -1) +struct tbf_ul_fsm_ctx { + union { /* back pointer. union used to easily access superclass from ctx */ + struct gprs_rlcmac_tbf *tbf; + struct gprs_rlcmac_ul_tbf *ul_tbf; + }; + uint32_t state_flags; +}; -extern struct osmo_fsm tbf_fsm; +extern struct osmo_fsm tbf_dl_fsm; +extern struct osmo_fsm tbf_ul_fsm; diff --git a/src/tbf_ul.cpp b/src/tbf_ul.cpp index 74b2636b..3746130f 100644 --- a/src/tbf_ul.cpp +++ b/src/tbf_ul.cpp @@ -13,10 +13,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <bts.h> @@ -33,6 +29,7 @@ #include <gprs_ms.h> #include <llc.h> #include "pcu_utils.h" +#include "alloc_algo.h" extern "C" { #include <osmocom/core/msgb.h> @@ -108,10 +105,9 @@ static int ul_tbf_dtor(struct gprs_rlcmac_ul_tbf *tbf) } /* Generic function to alloc a UL TBF, later configured to be assigned either over CCCH or PACCH */ -struct gprs_rlcmac_ul_tbf *tbf_alloc_ul_tbf(struct gprs_rlcmac_bts *bts, GprsMs *ms, int8_t use_trx, bool single_slot) +struct gprs_rlcmac_ul_tbf *ul_tbf_alloc(struct gprs_rlcmac_bts *bts, struct GprsMs *ms) { struct gprs_rlcmac_ul_tbf *tbf; - int rc; OSMO_ASSERT(ms != NULL); @@ -124,122 +120,38 @@ struct gprs_rlcmac_ul_tbf *tbf_alloc_ul_tbf(struct gprs_rlcmac_bts *bts, GprsMs talloc_set_destructor(tbf, ul_tbf_dtor); new (tbf) gprs_rlcmac_ul_tbf(bts, ms); - rc = tbf->setup(use_trx, single_slot); - - /* if no resource */ - if (rc < 0) { - talloc_free(tbf); - return NULL; - } - - if (tbf->is_egprs_enabled()) - tbf->set_window_size(); - - tbf->m_ul_egprs_ctrs = rate_ctr_group_alloc(tbf, - &tbf_ul_egprs_ctrg_desc, tbf->m_ctrs->idx); - tbf->m_ul_gprs_ctrs = rate_ctr_group_alloc(tbf, - &tbf_ul_gprs_ctrg_desc, tbf->m_ctrs->idx); - if (!tbf->m_ul_egprs_ctrs || !tbf->m_ul_gprs_ctrs) { - LOGPTBF(tbf, LOGL_ERROR, "Couldn't allocate TBF UL counters\n"); - talloc_free(tbf); - return NULL; - } - - llist_add_tail(tbf_trx_list(tbf), &tbf->trx->ul_tbfs); bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_UL_ALLOCATED); return tbf; } -/* Alloc a UL TBF to be assigned over PACCH */ -gprs_rlcmac_ul_tbf *tbf_alloc_ul_pacch(struct gprs_rlcmac_bts *bts, GprsMs *ms, int8_t use_trx) -{ - struct gprs_rlcmac_ul_tbf *tbf; - - tbf = tbf_alloc_ul_tbf(bts, ms, use_trx, false); - if (!tbf) { - LOGPMS(ms, DTBF, LOGL_NOTICE, "No PDCH resource\n"); - /* Caller will most probably send a Imm Ass Reject after return */ - return NULL; - } - tbf->m_contention_resolution_done = 1; - osmo_fsm_inst_dispatch(tbf->state_fsm.fi, TBF_EV_ASSIGN_ADD_PACCH, NULL); - - return tbf; -} - -/* Alloc a UL TBF to be assigned over CCCH */ -struct gprs_rlcmac_ul_tbf *tbf_alloc_ul_ccch(struct gprs_rlcmac_bts *bts, struct GprsMs *ms) -{ - struct gprs_rlcmac_ul_tbf *tbf; - - tbf = tbf_alloc_ul_tbf(bts, ms, -1, true); - if (!tbf) { - LOGP(DTBF, LOGL_NOTICE, "No PDCH resource for Uplink TBF\n"); - /* Caller will most probably send a Imm Ass Reject after return */ - return NULL; - } - osmo_fsm_inst_dispatch(tbf->state_fsm.fi, TBF_EV_ASSIGN_ADD_CCCH, NULL); - tbf->contention_resolution_start(); - OSMO_ASSERT(tbf->ms()); - - return tbf; -} - -/* Create a temporary dummy TBF to Tx a ImmAssReject if allocating a new one during - * packet resource Request failed. This is similar as tbf_alloc_ul() but without - * calling tbf->setup() (in charge of TFI/USF allocation), and reusing resources - * from Packet Resource Request we received. See TS 44.060 sec 7.1.3.2.1 */ -struct gprs_rlcmac_ul_tbf *handle_tbf_reject(struct gprs_rlcmac_bts *bts, - GprsMs *ms, uint8_t trx_no, uint8_t ts) -{ - struct gprs_rlcmac_ul_tbf *ul_tbf = NULL; - struct gprs_rlcmac_trx *trx = &bts->trx[trx_no]; - OSMO_ASSERT(ms); - - ul_tbf = talloc(tall_pcu_ctx, struct gprs_rlcmac_ul_tbf); - if (!ul_tbf) - return ul_tbf; - talloc_set_destructor(ul_tbf, ul_tbf_dtor); - new (ul_tbf) gprs_rlcmac_ul_tbf(bts, ms); - - ul_tbf->control_ts = ts; - ul_tbf->trx = trx; - ul_tbf->m_ctrs = rate_ctr_group_alloc(ul_tbf, &tbf_ctrg_desc, next_tbf_ctr_group_id++); - ul_tbf->m_ul_egprs_ctrs = rate_ctr_group_alloc(ul_tbf, - &tbf_ul_egprs_ctrg_desc, - ul_tbf->m_ctrs->idx); - ul_tbf->m_ul_gprs_ctrs = rate_ctr_group_alloc(ul_tbf, - &tbf_ul_gprs_ctrg_desc, - ul_tbf->m_ctrs->idx); - if (!ul_tbf->m_ctrs || !ul_tbf->m_ul_egprs_ctrs || !ul_tbf->m_ul_gprs_ctrs) { - LOGPTBF(ul_tbf, LOGL_ERROR, "Cound not allocate TBF UL rate counters\n"); - talloc_free(ul_tbf); - return NULL; - } - - ms_attach_tbf(ms, ul_tbf); - llist_add(tbf_trx_list((struct gprs_rlcmac_tbf *)ul_tbf), &trx->ul_tbfs); - bts_do_rate_ctr_inc(ul_tbf->bts, CTR_TBF_UL_ALLOCATED); - osmo_fsm_inst_dispatch(ul_tbf->state_fsm.fi, TBF_EV_ASSIGN_ADD_PACCH, NULL); - osmo_fsm_inst_dispatch(ul_tbf->ul_ass_fsm.fi, TBF_UL_ASS_EV_SCHED_ASS_REJ, NULL); - - return ul_tbf; -} - gprs_rlcmac_ul_tbf::gprs_rlcmac_ul_tbf(struct gprs_rlcmac_bts *bts_, GprsMs *ms) : gprs_rlcmac_tbf(bts_, ms, GPRS_RLCMAC_UL_TBF), m_rx_counter(0), - m_contention_resolution_done(0), + m_contention_resolution_done(true), m_ul_gprs_ctrs(NULL), m_ul_egprs_ctrs(NULL) { memset(&m_usf, USF_INVALID, sizeof(m_usf)); + memset(&state_fsm, 0, sizeof(state_fsm)); + state_fsm.ul_tbf = this; + state_fi = osmo_fsm_inst_alloc(&tbf_ul_fsm, this, &state_fsm, LOGL_INFO, NULL); + OSMO_ASSERT(state_fi); + memset(&ul_ack_fsm, 0, sizeof(ul_ack_fsm)); ul_ack_fsm.tbf = this; ul_ack_fsm.fi = osmo_fsm_inst_alloc(&tbf_ul_ack_fsm, this, &ul_ack_fsm, LOGL_INFO, NULL); + m_ul_egprs_ctrs = rate_ctr_group_alloc(this, &tbf_ul_egprs_ctrg_desc, m_ctrs->idx); + OSMO_ASSERT(m_ul_egprs_ctrs); + m_ul_gprs_ctrs = rate_ctr_group_alloc(this, &tbf_ul_gprs_ctrg_desc, m_ctrs->idx); + OSMO_ASSERT(m_ul_gprs_ctrs); + + /* This has to be called in child constructor because enable_egprs() + * uses the window() virtual function which is dependent on subclass. */ + if (ms_mode(m_ms) != GPRS) + enable_egprs(); } /* @@ -276,7 +188,7 @@ int gprs_rlcmac_ul_tbf::assemble_forward_llc(const gprs_rlc_data *_data) i + 1, frame->offset, frame->length, frame->is_complete); - m_llc.append_frame(data + frame->offset, frame->length); + llc_append_frame(&m_llc, data + frame->offset, frame->length); llc_consume(&m_llc, frame->length); } @@ -285,13 +197,14 @@ int gprs_rlcmac_ul_tbf::assemble_forward_llc(const gprs_rlc_data *_data) LOGPTBFUL(this, LOGL_DEBUG, "complete UL frame len=%d\n", llc_frame_length(&m_llc)); snd_ul_ud(); bts_do_rate_ctr_add(bts, CTR_LLC_UL_BYTES, llc_frame_length(&m_llc)); - m_llc.reset(); + llc_reset(&m_llc); } } return 0; } +/* 3GPP TS 44.060 sec 7a.2.1 Contention Resolution */ void gprs_rlcmac_ul_tbf::contention_resolution_start() { /* 3GPP TS 44.018 sec 11.1.2 Timers on the network side: "This timer is @@ -304,23 +217,29 @@ void gprs_rlcmac_ul_tbf::contention_resolution_start() * timeout only for one-phase packet access, since two-phase is handled * through SBA structs, which are freed by the PDCH UL Controller if the * single allocated block is lost. */ + m_contention_resolution_done = false; T_START(this, T3141, 3141, "Contention resolution (UL-TBF, CCCH)", true); } void gprs_rlcmac_ul_tbf::contention_resolution_success() { - if (m_contention_resolution_done) - return; + /* now we must set this flag, so we are allowed to assign downlink + * TBF on PACCH. it is only allowed when TLLI is acknowledged + * (3GPP TS 44.060 sec 7.1.3.1). */ + m_contention_resolution_done = true; - /* 3GPP TS 44.060 sec 7a.2.1 Contention Resolution */ /* 3GPP TS 44.018 3.5.2.1.4 Packet access completion: The one phase packet access procedure is completed at a successful contention resolution. The mobile station has entered the packet transfer mode. Timer T3141 is stopped on the network side */ t_stop(T3141, "Contention resolution success (UL-TBF, CCCH)"); - /* now we must set this flag, so we are allowed to assign downlink - * TBF on PACCH. it is only allowed when TLLI is acknowledged. */ - m_contention_resolution_done = 1; + bts_do_rate_ctr_inc(bts, CTR_IMMEDIATE_ASSIGN_UL_TBF_CONTENTION_RESOLUTION_SUCCESS); + + /* Check if we can create a DL TBF to start sending the enqueued + * data. Otherwise it will be triggered later when it is reachable + * again. */ + if (ms_need_dl_tbf(ms()) && !tbf_ul_ack_waiting_cnf_final_ack(this)) + ms_new_dl_tbf_assigned_on_pacch(ms(), this); } /*! \brief receive data from PDCH/L1 */ @@ -339,6 +258,19 @@ int gprs_rlcmac_ul_tbf::rcv_data_block_acknowledged( "V(R)=%d)\n", rlc->tfi, this->m_window.v_q(), this->m_window.v_r()); + if (tbf_state(this) == TBF_ST_RELEASING) { + /* This may happen if MAX_N3101 is hit previously, moving the UL + * TBF to RELEASING state. Since we have an fn-advance where DL + * blocks are scheduled in advance, we may have requested USF for + * this UL TBF before triggering and hence we are now receiving a + * UL block from it. If this is the case, simply ignore the block. + */ + LOGPTBFUL(this, LOGL_INFO, + "UL DATA TFI=%d received (V(Q)=%d .. V(R)=%d) while in RELEASING state, discarding\n", + rlc->tfi, this->m_window.v_q(), this->m_window.v_r()); + return 0; + } + /* process RSSI */ gprs_rlcmac_rssi(this, rssi); @@ -371,7 +303,7 @@ int gprs_rlcmac_ul_tbf::rcv_data_block_acknowledged( rdbi->bsn, m_window.v_q(), m_window.mod_sns(m_window.v_q() + ws - 1)); continue; - } else if (m_window.is_received(rdbi->bsn)) { + } else if (m_window.m_v_n.is_received(rdbi->bsn)) { LOGPTBFUL(this, LOGL_DEBUG, "BSN %d already received\n", rdbi->bsn); continue; @@ -429,8 +361,8 @@ int gprs_rlcmac_ul_tbf::rcv_data_block_acknowledged( LOGPTBFUL(this, LOGL_INFO, "Decoded premier TLLI=0x%08x of UL DATA TFI=%d.\n", new_tlli, rlc->tfi); - update_ms(new_tlli, GPRS_RLCMAC_UL_TBF); - bts_pch_timer_stop(bts, ms_imsi(ms())); + ms_update_announced_tlli(ms(), new_tlli); + osmo_fsm_inst_dispatch(this->state_fi, TBF_EV_FIRST_UL_DATA_RECVD, NULL); } else if (new_tlli != GSM_RESERVED_TMSI && new_tlli != tlli()) { LOGPTBFUL(this, LOGL_NOTICE, "Decoded TLLI=%08x mismatch on UL DATA TFI=%d. (Ignoring due to contention resolution)\n", @@ -472,7 +404,7 @@ int gprs_rlcmac_ul_tbf::rcv_data_block_acknowledged( rdbi->bsn, rdbi->cv); if (rdbi->cv == 0) { LOGPTBFUL(this, LOGL_DEBUG, "Finished with UL TBF\n"); - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_LAST_UL_DATA_RECVD, NULL); + osmo_fsm_inst_dispatch(this->state_fi, TBF_EV_LAST_UL_DATA_RECVD, NULL); /* Reset N3103 counter. */ this->n_reset(N3103); } @@ -535,7 +467,7 @@ int gprs_rlcmac_ul_tbf::snd_ul_ud() LOGP(DBSSGP, LOGL_INFO, "LLC [PCU -> SGSN] %s len=%d\n", tbf_name(this), llc_frame_length(&m_llc)); if (!bctx) { LOGP(DBSSGP, LOGL_ERROR, "No bctx\n"); - m_llc.reset_frame_space(); + llc_reset_frame_space(&m_llc); return -EIO; } @@ -547,7 +479,7 @@ int gprs_rlcmac_ul_tbf::snd_ul_ud() qos_profile[2] = QOS_PROFILE; bssgp_tx_ul_ud(bctx, tlli(), qos_profile, llc_pdu); - m_llc.reset_frame_space(); + llc_reset_frame_space(&m_llc); return 0; } @@ -743,13 +675,63 @@ gprs_rlc_window *gprs_rlcmac_ul_tbf::window() return &m_window; } +void gprs_rlcmac_ul_tbf::apply_allocated_resources(const struct alloc_resources_res *res) +{ + uint8_t ts; + + if (this->trx) + llist_del(&this->m_trx_list.list); + + llist_add(&this->m_trx_list.list, &res->trx->ul_tbfs); + + this->trx = res->trx; + this->upgrade_to_multislot = res->upgrade_to_multislot; + + for (ts = 0; ts < ARRAY_SIZE(trx->pdch); ts++) { + struct gprs_rlcmac_pdch *pdch = &trx->pdch[ts]; + OSMO_ASSERT(!this->pdch[pdch->ts_no]); + if (!(res->ass_slots_mask & (1 << ts))) + continue; + LOGPTBFUL(this, LOGL_DEBUG, "Assigning TS=%u TFI=%d USF=%u\n", + ts, res->tfi, res->usf[ts]); + OSMO_ASSERT(res->usf[ts] >= 0); + + this->m_tfi = res->tfi; + this->m_usf[pdch->ts_no] = res->usf[ts]; + + this->pdch[pdch->ts_no] = pdch; + pdch->attach_tbf(this); + } + + /* assign initial control ts */ + tbf_assign_control_ts(this); + + /* res.ass_slots_mask == 0 -> special case for Rejected UL TBFs, + * see ms_new_ul_tbf_rejected_pacch() */ + if (res->ass_slots_mask != 0) { + LOGPTBF(this, LOGL_INFO, + "Allocated: trx = %d, ul_slots = %02x, dl_slots = %02x\n", + this->trx->trx_no, ul_slots(), dl_slots()); + + if (tbf_is_egprs_enabled(this)) + this->set_window_size(); + } + + tbf_update_state_fsm_name(this); +} + +void ul_tbf_apply_allocated_resources(struct gprs_rlcmac_ul_tbf *ul_tbf, const struct alloc_resources_res *res) +{ + ul_tbf->apply_allocated_resources(res); +} + void gprs_rlcmac_ul_tbf::usf_timeout() { if (n_inc(N3101)) - osmo_fsm_inst_dispatch(this->state_fsm.fi, TBF_EV_MAX_N3101, NULL); + osmo_fsm_inst_dispatch(this->state_fi, TBF_EV_MAX_N3101, NULL); } -struct gprs_rlcmac_ul_tbf *as_ul_tbf(struct gprs_rlcmac_tbf *tbf) +struct gprs_rlcmac_ul_tbf *tbf_as_ul_tbf(struct gprs_rlcmac_tbf *tbf) { if (tbf && tbf->direction == GPRS_RLCMAC_UL_TBF) return static_cast<gprs_rlcmac_ul_tbf *>(tbf); @@ -757,6 +739,14 @@ struct gprs_rlcmac_ul_tbf *as_ul_tbf(struct gprs_rlcmac_tbf *tbf) return NULL; } +const struct gprs_rlcmac_ul_tbf *tbf_as_ul_tbf_const(const struct gprs_rlcmac_tbf *tbf) +{ + if (tbf && tbf->direction == GPRS_RLCMAC_UL_TBF) + return static_cast<const gprs_rlcmac_ul_tbf *>(tbf); + else + return NULL; +} + void tbf_usf_timeout(struct gprs_rlcmac_ul_tbf *tbf) { tbf->usf_timeout(); @@ -773,6 +763,11 @@ struct osmo_fsm_inst *tbf_ul_ack_fi(const struct gprs_rlcmac_ul_tbf *tbf) return tbf->ul_ack_fsm.fi; } +void ul_tbf_contention_resolution_start(struct gprs_rlcmac_ul_tbf *tbf) +{ + tbf->contention_resolution_start(); +} + void ul_tbf_contention_resolution_success(struct gprs_rlcmac_ul_tbf *tbf) { return tbf->contention_resolution_success(); diff --git a/src/tbf_ul.h b/src/tbf_ul.h index 0dc2336d..7a4c70c7 100644 --- a/src/tbf_ul.h +++ b/src/tbf_ul.h @@ -11,10 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -24,10 +20,12 @@ #include <stdbool.h> #include "tbf.h" +#include "rlc_window_ul.h" #ifdef __cplusplus extern "C" { #endif +#include <tbf_fsm.h> #include <tbf_ul_ack_fsm.h> #ifdef __cplusplus } @@ -61,8 +59,9 @@ enum tbf_egprs_ul_counters { struct gprs_rlcmac_ul_tbf : public gprs_rlcmac_tbf { gprs_rlcmac_ul_tbf(struct gprs_rlcmac_bts *bts, GprsMs *ms); - ~gprs_rlcmac_ul_tbf(); - gprs_rlc_window *window(); + ~gprs_rlcmac_ul_tbf(void); + gprs_rlc_window *window(void); + void apply_allocated_resources(const struct alloc_resources_res *res); /* blocks were acked */ int rcv_data_block_acknowledged( const struct gprs_rlc_data_info *rlc, @@ -71,7 +70,7 @@ struct gprs_rlcmac_ul_tbf : public gprs_rlcmac_tbf { /* TODO: extract LLC class? */ int assemble_forward_llc(const gprs_rlc_data *data); - int snd_ul_ud(); + int snd_ul_ud(void); egprs_rlc_ul_reseg_bsn_state handle_egprs_ul_spb( const struct gprs_rlc_data_info *rlc, @@ -88,12 +87,12 @@ struct gprs_rlcmac_ul_tbf : public gprs_rlcmac_tbf { struct gprs_rlc_data *block, uint8_t *data, const uint8_t block_idx); - uint16_t window_size() const; - void set_window_size(); + uint16_t window_size(void) const; + void set_window_size(void); void update_coding_scheme_counter_ul(enum CodingScheme cs); - void usf_timeout(); - void contention_resolution_start(); - void contention_resolution_success(); + void usf_timeout(void); + void contention_resolution_start(void); + void contention_resolution_success(void); /* Please note that all variables here will be reset when changing * from WAIT RELEASE back to FLOW state (re-use of TBF). @@ -102,12 +101,13 @@ struct gprs_rlcmac_ul_tbf : public gprs_rlcmac_tbf { */ int32_t m_rx_counter; /* count all received blocks */ uint8_t m_usf[8]; /* list USFs per PDCH (timeslot), initialized to USF_INVALID */ - uint8_t m_contention_resolution_done; /* set after done */ + bool m_contention_resolution_done; /* set after done */ struct rate_ctr_group *m_ul_gprs_ctrs; struct rate_ctr_group *m_ul_egprs_ctrs; - struct tbf_ul_ass_fsm_ctx ul_ack_fsm; + struct tbf_ul_fsm_ctx state_fsm; + struct tbf_ul_ack_fsm_ctx ul_ack_fsm; protected: void maybe_schedule_uplink_acknack(const gprs_rlc_data_info *rlc, bool countdown_finished); @@ -125,11 +125,6 @@ inline uint16_t gprs_rlcmac_ul_tbf::window_size() const return m_window.ws(); } -struct gprs_rlcmac_ul_tbf *tbf_alloc_ul_tbf(struct gprs_rlcmac_bts *bts, GprsMs *ms, int8_t use_trx, bool single_slot); -struct gprs_rlcmac_ul_tbf *tbf_alloc_ul_pacch(struct gprs_rlcmac_bts *bts, GprsMs *ms, int8_t use_trx); -struct gprs_rlcmac_ul_tbf *tbf_alloc_ul_ccch(struct gprs_rlcmac_bts *bts, struct GprsMs *ms); -struct gprs_rlcmac_ul_tbf *handle_tbf_reject(struct gprs_rlcmac_bts *bts, - GprsMs *ms, uint8_t trx_no, uint8_t ts_no); #else /* ifdef __cplusplus */ struct gprs_rlcmac_ul_tbf; @@ -139,15 +134,29 @@ struct gprs_rlcmac_ul_tbf; #ifdef __cplusplus extern "C" { #endif +struct gprs_rlcmac_ul_tbf *ul_tbf_alloc(struct gprs_rlcmac_bts *bts, struct GprsMs *ms); void update_tbf_ta(struct gprs_rlcmac_ul_tbf *tbf, int8_t ta_delta); void set_tbf_ta(struct gprs_rlcmac_ul_tbf *tbf, uint8_t ta); -struct gprs_rlcmac_ul_tbf *as_ul_tbf(struct gprs_rlcmac_tbf *tbf); +struct gprs_rlcmac_ul_tbf *tbf_as_ul_tbf(struct gprs_rlcmac_tbf *tbf); +const struct gprs_rlcmac_ul_tbf *tbf_as_ul_tbf_const(const struct gprs_rlcmac_tbf *tbf); void tbf_usf_timeout(struct gprs_rlcmac_ul_tbf *tbf); +void ul_tbf_apply_allocated_resources(struct gprs_rlcmac_ul_tbf *ul_tbf, const struct alloc_resources_res *res); +void ul_tbf_contention_resolution_start(struct gprs_rlcmac_ul_tbf *tbf); +void ul_tbf_contention_resolution_success(struct gprs_rlcmac_ul_tbf *tbf); bool ul_tbf_contention_resolution_done(const struct gprs_rlcmac_ul_tbf *tbf); struct osmo_fsm_inst *tbf_ul_ack_fi(const struct gprs_rlcmac_ul_tbf *tbf); -void ul_tbf_contention_resolution_success(struct gprs_rlcmac_ul_tbf *tbf); -#define LOGPTBFUL(tbf, level, fmt, args...) LOGP(DTBFUL, level, "%s " fmt, tbf_name(tbf), ## args) +static inline struct gprs_rlcmac_tbf *ul_tbf_as_tbf(struct gprs_rlcmac_ul_tbf *ul_tbf) +{ + return (struct gprs_rlcmac_tbf *)ul_tbf; +} + +static inline const struct gprs_rlcmac_tbf *ul_tbf_as_tbf_const(const struct gprs_rlcmac_ul_tbf *ul_tbf) +{ + return (const struct gprs_rlcmac_tbf *)ul_tbf; +} + +#define LOGPTBFUL(ul_tbf, level, fmt, args...) LOGP(DTBFUL, level, "%s " fmt, tbf_name(ul_tbf_as_tbf_const(ul_tbf)), ## args) #ifdef __cplusplus } #endif diff --git a/src/tbf_ul_ack_fsm.c b/src/tbf_ul_ack_fsm.c index 32e3533f..2fa20d70 100644 --- a/src/tbf_ul_ack_fsm.c +++ b/src/tbf_ul_ack_fsm.c @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <unistd.h> @@ -37,13 +33,13 @@ #define X(s) (1 << (s)) -const struct osmo_tdef_state_timeout tbf_ul_ack_fsm_timeouts[32] = { +static const struct osmo_tdef_state_timeout tbf_ul_ack_fsm_timeouts[32] = { [TBF_UL_ACK_ST_NONE] = {}, [TBF_UL_ACK_ST_SCHED_UL_ACK] = {}, [TBF_UL_ACK_ST_WAIT_ACK] = {}, }; -const struct value_string tbf_ul_ack_fsm_event_names[] = { +static const struct value_string tbf_ul_ack_fsm_event_names[] = { { TBF_UL_ACK_EV_SCHED_ACK, "SCHED_ACK" }, { TBF_UL_ACK_EV_CREATE_RLCMAC_MSG, "CREATE_RLCMAC_MSG" }, { TBF_UL_ACK_EV_RX_CTRL_ACK, "RX_CTRL_ACK" }, @@ -51,6 +47,15 @@ const struct value_string tbf_ul_ack_fsm_event_names[] = { { 0, NULL } }; +/* Transition to a state, using the T timer defined in tbf_ul_ack_fsm_timeouts. + * The actual timeout value is in turn obtained from conn->T_defs. + * Assumes local variable fi exists. */ +#define tbf_ul_ack_fsm_state_chg(fi, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \ + tbf_ul_ack_fsm_timeouts, \ + the_pcu->T_defs, \ + -1) + static struct msgb *create_ul_ack_nack(const struct tbf_ul_ack_fsm_ctx *ctx, const struct tbf_ul_ack_ev_create_rlcmac_msg_ctx *d, bool final) @@ -59,11 +64,10 @@ static struct msgb *create_ul_ack_nack(const struct tbf_ul_ack_fsm_ctx *ctx, int rc; unsigned int rrbp = 0; uint32_t new_poll_fn = 0; - struct gprs_rlcmac_tbf *tbf = (struct gprs_rlcmac_tbf *)ctx->tbf; - struct GprsMs *ms = tbf_ms(tbf); + struct gprs_rlcmac_tbf *tbf = ul_tbf_as_tbf(ctx->tbf); if (final) { - rc = tbf_check_polling(tbf, d->fn, d->ts, &new_poll_fn, &rrbp); + rc = tbf_check_polling(tbf, d->pdch, d->fn, &new_poll_fn, &rrbp); if (rc < 0) return NULL; } @@ -81,21 +85,11 @@ static struct msgb *create_ul_ack_nack(const struct tbf_ul_ack_fsm_ctx *ctx, bitvec_pack(ack_vec, msgb_put(msg, 23)); bitvec_free(ack_vec); - /* TS 44.060 7a.2.1.1: "The contention resolution is completed on - * the network side when the network receives an RLC data block that - * comprises the TLLI value that identifies the mobile station and the - * TFI value associated with the TBF." - * However, it's handier for us to mark contention resolution success - * here since according to spec upon rx UL ACK is the time at which MS - * realizes contention resolution succeeds. */ - if (ms_tlli(ms) != GSM_RESERVED_TMSI) - ul_tbf_contention_resolution_success(ctx->tbf); - if (final) { - tbf_set_polling(tbf, new_poll_fn, d->ts, PDCH_ULC_POLL_UL_ACK); - LOGPTBFUL(tbf, LOGL_DEBUG, - "Scheduled UL Acknowledgement polling on PACCH (FN=%d, TS=%d)\n", - new_poll_fn, d->ts); + tbf_set_polling(tbf, d->pdch, new_poll_fn, PDCH_ULC_POLL_UL_ACK); + LOGPTBFUL(ctx->tbf, LOGL_DEBUG, + "Scheduled UL Acknowledgement polling on PACCH (FN=%d, TS=%d)\n", + new_poll_fn, d->pdch->ts_no); } return msg; @@ -115,7 +109,8 @@ static void st_none(struct osmo_fsm_inst *fi, uint32_t event, void *data) static void st_sched_ul_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct tbf_ul_ack_fsm_ctx *ctx = (struct tbf_ul_ack_fsm_ctx *)fi->priv; - struct gprs_rlcmac_tbf *tbf = (struct gprs_rlcmac_tbf *)ctx->tbf; + struct gprs_rlcmac_ul_tbf *tbf = ctx->tbf; + struct GprsMs *ms = tbf_ms(ul_tbf_as_tbf(tbf)); struct tbf_ul_ack_ev_create_rlcmac_msg_ctx *data_ctx; bool final; @@ -126,7 +121,7 @@ static void st_sched_ul_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data break; case TBF_UL_ACK_EV_CREATE_RLCMAC_MSG: data_ctx = (struct tbf_ul_ack_ev_create_rlcmac_msg_ctx *)data; - final = tbf_state(tbf) == TBF_ST_FINISHED; + final = tbf_state(ul_tbf_as_tbf(tbf)) == TBF_ST_FINISHED; data_ctx->msg = create_ul_ack_nack(ctx, data_ctx, final); if (!data_ctx->msg) return; @@ -134,6 +129,28 @@ static void st_sched_ul_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data tbf_ul_ack_fsm_state_chg(fi, TBF_UL_ACK_ST_WAIT_ACK); else tbf_ul_ack_fsm_state_chg(fi, TBF_UL_ACK_ST_NONE); + + /* TS 44.060 7a.2.1.1: "The contention resolution is completed on + * the network side when the network receives an RLC data block that + * comprises the TLLI value that identifies the mobile station and the + * TFI value associated with the TBF." (see TBF_EV_FIRST_UL_DATA_RECVD). + * + * However, it's handier for us to mark contention resolution success here + * since upon rx UL ACK is the time at which MS realizes contention resolution + * succeeds: + * TS 44.060 7.1.2.3: "The contention resolution is successfully completed + * on the mobile station side when the mobile station receives a + * PACKET UPLINK ACK/NACK" + * + * This event must be triggered here *after* potentially transitioning + * to ST_WAIT_ACK above, so that gprs_ms knows whether it can create a + * DL TBF on PACCH of the UL_TBF or not (not possible if we are in + * ST_WAIT_ACK, since UL TBF is terminating sending the final PKT CTRL + * ACK). + */ + if (ms_tlli(ms) != GSM_RESERVED_TMSI && !ul_tbf_contention_resolution_done(ctx->tbf)) + osmo_fsm_inst_dispatch(tbf_state_fi(ul_tbf_as_tbf(ctx->tbf)), TBF_EV_CONTENTION_RESOLUTION_MS_SUCCESS, NULL); + break; default: OSMO_ASSERT(0); @@ -143,7 +160,7 @@ static void st_sched_ul_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data static void st_wait_ctrl_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct tbf_ul_ack_fsm_ctx *ctx = (struct tbf_ul_ack_fsm_ctx *)fi->priv; - struct gprs_rlcmac_tbf *tbf = (struct gprs_rlcmac_tbf *)ctx->tbf; + struct gprs_rlcmac_ul_tbf *tbf = ctx->tbf; switch (event) { case TBF_UL_ACK_EV_SCHED_ACK: @@ -153,9 +170,9 @@ static void st_wait_ctrl_ack(struct osmo_fsm_inst *fi, uint32_t event, void *dat tbf_ul_ack_fsm_state_chg(fi, TBF_UL_ACK_ST_NONE); break; case TBF_UL_ACK_EV_POLL_TIMEOUT: - LOGPTBF(tbf, LOGL_NOTICE, + LOGPTBFUL(tbf, LOGL_NOTICE, "Timeout for polling PACKET CONTROL ACK for PACKET UPLINK ACK: %s\n", - tbf_rlcmac_diag(tbf)); + tbf_rlcmac_diag(ul_tbf_as_tbf(tbf))); /* Reschedule Ul Ack/NAck */ tbf_ul_ack_fsm_state_chg(fi, TBF_UL_ACK_ST_SCHED_UL_ACK); break; @@ -220,39 +237,44 @@ static __attribute__((constructor)) void tbf_ul_ack_fsm_init(void) } -struct msgb *tbf_ul_ack_create_rlcmac_msg(const struct gprs_rlcmac_tbf *tbf, uint32_t fn, uint8_t ts) +struct msgb *tbf_ul_ack_create_rlcmac_msg(const struct gprs_rlcmac_ul_tbf *ul_tbf, + const struct gprs_rlcmac_pdch *pdch, + uint32_t fn) { int rc; struct tbf_ul_ack_ev_create_rlcmac_msg_ctx data_ctx = { + .pdch = pdch, .fn = fn, - .ts = ts, .msg = NULL, }; - OSMO_ASSERT(tbf_direction(tbf) == GPRS_RLCMAC_UL_TBF); - rc = osmo_fsm_inst_dispatch(tbf_ul_ack_fi((const struct gprs_rlcmac_ul_tbf *)tbf), TBF_UL_ACK_EV_CREATE_RLCMAC_MSG, &data_ctx); + rc = osmo_fsm_inst_dispatch(tbf_ul_ack_fi(ul_tbf), TBF_UL_ACK_EV_CREATE_RLCMAC_MSG, &data_ctx); if (rc != 0 || !data_ctx.msg) return NULL; return data_ctx.msg; } -bool tbf_ul_ack_rts(const struct gprs_rlcmac_tbf *tbf) +bool tbf_ul_ack_rts(const struct gprs_rlcmac_ul_tbf *ul_tbf, const struct gprs_rlcmac_pdch *pdch) { - struct osmo_fsm_inst *fi = tbf_ul_ack_fi((const struct gprs_rlcmac_ul_tbf *)tbf); + struct osmo_fsm_inst *fi; + + if (!tbf_is_control_ts(ul_tbf_as_tbf_const(ul_tbf), pdch)) + return false; + + fi = tbf_ul_ack_fi(ul_tbf); return fi->state == TBF_UL_ACK_ST_SCHED_UL_ACK; } /* Did we already send the Final ACK and we are waiting for its confirmation (CTRL ACK) ? */ -bool tbf_ul_ack_waiting_cnf_final_ack(const struct gprs_rlcmac_tbf* tbf) +bool tbf_ul_ack_waiting_cnf_final_ack(const struct gprs_rlcmac_ul_tbf *ul_tbf) { - OSMO_ASSERT(tbf_direction(tbf) == GPRS_RLCMAC_UL_TBF); - struct osmo_fsm_inst *fi = tbf_ul_ack_fi((const struct gprs_rlcmac_ul_tbf *)tbf); + struct osmo_fsm_inst *fi = tbf_ul_ack_fi(ul_tbf); return fi->state == TBF_UL_ACK_ST_WAIT_ACK; } -bool tbf_ul_ack_exp_ctrl_ack(const struct gprs_rlcmac_tbf *tbf, uint32_t fn, uint8_t ts) +bool tbf_ul_ack_exp_ctrl_ack(const struct gprs_rlcmac_ul_tbf *ul_tbf, uint32_t fn, uint8_t ts) { - struct osmo_fsm_inst *fi = tbf_ul_ack_fi((const struct gprs_rlcmac_ul_tbf *)tbf); + struct osmo_fsm_inst *fi = tbf_ul_ack_fi(ul_tbf); return fi->state == TBF_UL_ACK_ST_WAIT_ACK; /* FIXME: validate FN and TS match: && ctx->poll_fn = fn && ctx->poll_ts == ts */ } diff --git a/src/tbf_ul_ack_fsm.h b/src/tbf_ul_ack_fsm.h index aaee5a3e..3f34f31a 100644 --- a/src/tbf_ul_ack_fsm.h +++ b/src/tbf_ul_ack_fsm.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -26,6 +22,7 @@ struct gprs_rlcmac_tbf; struct gprs_rlcmac_ul_tbf; +struct gprs_rlcmac_pdch; enum tbf_ul_ack_fsm_event { TBF_UL_ACK_EV_SCHED_ACK, /* Tx UL ACK/NACK is pending */ @@ -45,28 +42,20 @@ struct tbf_ul_ack_fsm_ctx { struct gprs_rlcmac_ul_tbf *tbf; /* back pointer */ }; -extern const struct osmo_tdef_state_timeout tbf_ul_ack_fsm_timeouts[32]; -/* Transition to a state, using the T timer defined in tbf_ul_ack_fsm_timeouts. - * The actual timeout value is in turn obtained from conn->T_defs. - * Assumes local variable fi exists. */ -#define tbf_ul_ack_fsm_state_chg(fi, NEXT_STATE) \ - osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \ - tbf_ul_ack_fsm_timeouts, \ - the_pcu->T_defs, \ - -1) - extern struct osmo_fsm tbf_ul_ack_fsm; /* passed as data in TBF_UL_ACK_EV_CREATE_RLCMAC_MSG */ struct tbf_ul_ack_ev_create_rlcmac_msg_ctx { + const struct gprs_rlcmac_pdch *pdch; /* TS where the created DL ctrl block is to be sent */ uint32_t fn; /* FN where the created DL ctrl block is to be sent */ - uint8_t ts; /* TS where the created DL ctrl block is to be sent */ struct msgb *msg; /* to be filled by FSM during event processing */ }; -struct msgb *tbf_ul_ack_create_rlcmac_msg(const struct gprs_rlcmac_tbf *tbf, uint32_t fn, uint8_t ts); -bool tbf_ul_ack_rts(const struct gprs_rlcmac_tbf *tbf); -bool tbf_ul_ack_waiting_cnf_final_ack(const struct gprs_rlcmac_tbf *tbf); -bool tbf_ul_ack_exp_ctrl_ack(const struct gprs_rlcmac_tbf *tbf, uint32_t fn, uint8_t ts); +struct msgb *tbf_ul_ack_create_rlcmac_msg(const struct gprs_rlcmac_ul_tbf *ul_tbf, + const struct gprs_rlcmac_pdch *pdch, + uint32_t fn); +bool tbf_ul_ack_rts(const struct gprs_rlcmac_ul_tbf *ul_tbf, const struct gprs_rlcmac_pdch *pdch); +bool tbf_ul_ack_waiting_cnf_final_ack(const struct gprs_rlcmac_ul_tbf *ul_tbf); +bool tbf_ul_ack_exp_ctrl_ack(const struct gprs_rlcmac_ul_tbf *ul_tbf, uint32_t fn, uint8_t ts); diff --git a/src/tbf_ul_ass_fsm.c b/src/tbf_ul_ass_fsm.c index ab23fbdb..6f88f112 100644 --- a/src/tbf_ul_ass_fsm.c +++ b/src/tbf_ul_ass_fsm.c @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <unistd.h> @@ -35,14 +31,14 @@ #define X(s) (1 << (s)) -const struct osmo_tdef_state_timeout tbf_ul_ass_fsm_timeouts[32] = { +static const struct osmo_tdef_state_timeout tbf_ul_ass_fsm_timeouts[32] = { [TBF_UL_ASS_NONE] = {}, - [TBF_UL_ASS_SEND_ASS] = {}, + [TBF_UL_ASS_SEND_ASS] = { .keep_timer = true }, [TBF_UL_ASS_SEND_ASS_REJ] = {}, - [TBF_UL_ASS_WAIT_ACK] = {}, + [TBF_UL_ASS_WAIT_ACK] = { .keep_timer = true }, }; -const struct value_string tbf_ul_ass_fsm_event_names[] = { +static const struct value_string tbf_ul_ass_fsm_event_names[] = { { TBF_UL_ASS_EV_SCHED_ASS, "SCHED_ASS" }, { TBF_UL_ASS_EV_SCHED_ASS_REJ, "SCHED_ASS_REJ" }, { TBF_UL_ASS_EV_CREATE_RLCMAC_MSG, "CREATE_RLCMAC_MSG" }, @@ -52,6 +48,15 @@ const struct value_string tbf_ul_ass_fsm_event_names[] = { { 0, NULL } }; +/* Transition to a state, using the T timer defined in tbf_ul_ass_fsm_timeouts. + * The actual timeout value is in turn obtained from conn->T_defs. + * Assumes local variable fi exists. */ +#define tbf_ul_ass_fsm_state_chg(fi, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \ + tbf_ul_ass_fsm_timeouts, \ + the_pcu->T_defs, \ + -1) + static struct msgb *create_packet_access_reject(const struct tbf_ul_ass_fsm_ctx *ctx) { struct msgb *msg; @@ -64,7 +69,7 @@ static struct msgb *create_packet_access_reject(const struct tbf_ul_ass_fsm_ctx bitvec_unhex(packet_access_rej, DUMMY_VEC); write_packet_access_reject(packet_access_rej, ms_tlli(ms), - osmo_tdef_get(ms->bts->T_defs_bts, 3172, OSMO_TDEF_MS, -1)); + osmo_tdef_get(ms->bts->pcu->T_defs, 3172, OSMO_TDEF_MS, -1)); bts_do_rate_ctr_inc(ms->bts, CTR_PKT_ACCESS_REJ); @@ -75,7 +80,7 @@ static struct msgb *create_packet_access_reject(const struct tbf_ul_ass_fsm_ctx } -struct msgb *create_packet_ul_assign(const struct tbf_ul_ass_fsm_ctx *ctx, +static struct msgb *create_packet_ul_assign(const struct tbf_ul_ass_fsm_ctx *ctx, const struct tbf_ul_ass_ev_create_rlcmac_msg_ctx *d) { struct msgb *msg = NULL; @@ -87,7 +92,7 @@ struct msgb *create_packet_ul_assign(const struct tbf_ul_ass_fsm_ctx *ctx, unsigned int rrbp; uint32_t new_poll_fn; - rc = tbf_check_polling(ctx->tbf, d->fn, d->ts, &new_poll_fn, &rrbp); + rc = tbf_check_polling(ctx->tbf, d->pdch, d->fn, &new_poll_fn, &rrbp); if (rc < 0) return NULL; @@ -110,7 +115,12 @@ struct msgb *create_packet_ul_assign(const struct tbf_ul_ass_fsm_ctx *ctx, }; bitvec_unhex(&bv, DUMMY_VEC); - LOGPTBFUL((const struct gprs_rlcmac_tbf *)new_tbf, LOGL_INFO, "start Packet Uplink Assignment (PACCH)\n"); + if (ctx->tbf != ul_tbf_as_tbf_const(new_tbf)) + LOGPTBF(ctx->tbf, LOGL_INFO, "start Packet Uplink Assignment (PACCH) for %s\n", + tbf_name(ul_tbf_as_tbf_const(new_tbf))); + else + LOGPTBF(ctx->tbf, LOGL_INFO, "start Packet Uplink Assignment (PACCH)\n"); + mac_control_block = (RlcMacDownlink_t *)talloc_zero(ctx->tbf, RlcMacDownlink_t); tlli = ms_tlli(ms); write_packet_uplink_assignment(mac_control_block, tbf_tfi(ctx->tbf), @@ -127,9 +137,9 @@ struct msgb *create_packet_ul_assign(const struct tbf_ul_ass_fsm_ctx *ctx, LOGP(DTBF, LOGL_DEBUG, "------------------------- TX : Packet Uplink Assignment -------------------------\n"); bts_do_rate_ctr_inc(ms->bts, CTR_PKT_UL_ASSIGNMENT); - tbf_set_polling(ctx->tbf, new_poll_fn, d->ts, PDCH_ULC_POLL_UL_ASS); - LOGPTBFUL(ctx->tbf, LOGL_INFO, "Scheduled UL Assignment polling on PACCH (FN=%d, TS=%d)\n", - new_poll_fn, d->ts); + tbf_set_polling(ctx->tbf, d->pdch, new_poll_fn, PDCH_ULC_POLL_UL_ASS); + LOGPTBF(ctx->tbf, LOGL_INFO, "Scheduled UL Assignment polling on PACCH (FN=%d, TS=%d)\n", + new_poll_fn, d->pdch->ts_no); talloc_free(mac_control_block); return msg; @@ -154,7 +164,7 @@ static void st_none_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) val = osmo_tdef_get(the_pcu->T_defs, fi->T, OSMO_TDEF_MS, -1); sec = val / 1000; micro = (val % 1000) * 1000; - LOGPTBF(ctx->tbf, LOGL_DEBUG, "starting timer X2000 [reject (PACCH)] with %u sec. %u microsec\n", + LOGPTBF(ctx->tbf, LOGL_DEBUG, "Starting timer X2000 [reject (PACCH)] with %u sec. %u microsec\n", sec, micro); osmo_timer_schedule(&fi->timer, sec, micro); } @@ -178,6 +188,32 @@ static void st_none(struct osmo_fsm_inst *fi, uint32_t event, void *data) } } +static void st_send_ass_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct tbf_ul_ass_fsm_ctx *ctx = (struct tbf_ul_ass_fsm_ctx *)fi->priv; + unsigned long val; + unsigned int sec, micro; + struct GprsMs *ms = tbf_ms(ctx->tbf); + + /* Here it's point in time where we received PKT RES REQ or DL ACK/NACK to request a new UL TBF, + * so MS will be gone after T3168 (* 4 retrans, 8.1.1.1.2) if we are unable to seize it. + * Hence, attempt re-scheduling PKT UL ASS (states SEND_ASS<->WAIT_ACK ping-pong) until T3168 we + * announced (SI13) to the MS expires: + */ + if (prev_state == TBF_UL_ASS_NONE) { + /* tbf_free() called upon trigger */ + fi->T = 3168; + val = osmo_tdef_get(ms->bts->T_defs_bts, fi->T, OSMO_TDEF_MS, -1); + val *= 4; /* 4 PKT RES REQ retransmit */ + sec = val / 1000; + micro = (val % 1000) * 1000; + LOGPTBF(ctx->tbf, LOGL_DEBUG, "Starting timer T3168 [PKT UL ASS PACCH] with %u sec. %u microsec\n", + sec, micro); + osmo_timer_schedule(&fi->timer, sec, micro); + } + +} + static void st_send_ass(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct tbf_ul_ass_fsm_ctx *ctx = (struct tbf_ul_ass_fsm_ctx *)fi->priv; @@ -258,6 +294,7 @@ static int tbf_ul_ass_fsm_timer_cb(struct osmo_fsm_inst *fi) struct tbf_ul_ass_fsm_ctx *ctx = (struct tbf_ul_ass_fsm_ctx *)fi->priv; switch (fi->T) { case -2000: + case 3168: tbf_free(ctx->tbf); break; default: @@ -288,6 +325,7 @@ static struct osmo_fsm_state tbf_ul_ass_fsm_states[] = { X(TBF_UL_ASS_NONE), .name = "SEND_ASS", .action = st_send_ass, + .onenter = st_send_ass_on_enter, }, [TBF_UL_ASS_SEND_ASS_REJ] = { .in_event_mask = @@ -325,12 +363,14 @@ static __attribute__((constructor)) void tbf_ul_ass_fsm_init(void) } -struct msgb *tbf_ul_ass_create_rlcmac_msg(const struct gprs_rlcmac_tbf* tbf, uint32_t fn, uint8_t ts) +struct msgb *tbf_ul_ass_create_rlcmac_msg(const struct gprs_rlcmac_tbf *tbf, + const struct gprs_rlcmac_pdch *pdch, + uint32_t fn) { int rc; struct tbf_ul_ass_ev_create_rlcmac_msg_ctx data_ctx = { + .pdch = pdch, .fn = fn, - .ts = ts, .msg = NULL, }; @@ -340,8 +380,13 @@ struct msgb *tbf_ul_ass_create_rlcmac_msg(const struct gprs_rlcmac_tbf* tbf, uin return data_ctx.msg; } -bool tbf_ul_ass_rts(const struct gprs_rlcmac_tbf* tbf) +bool tbf_ul_ass_rts(const struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch) { - struct osmo_fsm_inst *fi = tbf_ul_ass_fi(tbf); + struct osmo_fsm_inst *fi; + + if (!tbf_is_control_ts(tbf, pdch)) + return false; + + fi = tbf_ul_ass_fi(tbf); return fi->state == TBF_UL_ASS_SEND_ASS || fi->state == TBF_UL_ASS_SEND_ASS_REJ; } diff --git a/src/tbf_ul_ass_fsm.h b/src/tbf_ul_ass_fsm.h index 34b81a7b..28a09dcd 100644 --- a/src/tbf_ul_ass_fsm.h +++ b/src/tbf_ul_ass_fsm.h @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #pragma once @@ -25,6 +21,7 @@ #include <gprs_pcu.h> struct gprs_rlcmac_tbf; +struct gprs_rlcmac_pdch; enum tbf_ul_ass_fsm_event { TBF_UL_ASS_EV_SCHED_ASS, /* Tx Uplink Assignment is pending */ @@ -47,26 +44,18 @@ struct tbf_ul_ass_fsm_ctx { struct gprs_rlcmac_tbf* tbf; /* back pointer */ }; -extern const struct osmo_tdef_state_timeout tbf_ul_ass_fsm_timeouts[32]; -/* Transition to a state, using the T timer defined in tbf_ul_ass_fsm_timeouts. - * The actual timeout value is in turn obtained from conn->T_defs. - * Assumes local variable fi exists. */ -#define tbf_ul_ass_fsm_state_chg(fi, NEXT_STATE) \ - osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \ - tbf_ul_ass_fsm_timeouts, \ - the_pcu->T_defs, \ - -1) - extern struct osmo_fsm tbf_ul_ass_fsm; /* passed as data in TBF_UL_ASS_EV_CREATE_RLCMAC_MSG */ struct tbf_ul_ass_ev_create_rlcmac_msg_ctx { + const struct gprs_rlcmac_pdch *pdch; /* TS where the created DL ctrl block is to be sent */ uint32_t fn; /* FN where the created DL ctrl block is to be sent */ - uint8_t ts; /* TS where the created DL ctrl block is to be sent */ struct msgb *msg; /* to be filled by FSM during event processing */ }; -struct msgb *tbf_ul_ass_create_rlcmac_msg(const struct gprs_rlcmac_tbf* tbf, uint32_t fn, uint8_t ts); -bool tbf_ul_ass_rts(const struct gprs_rlcmac_tbf* tbf); +struct msgb *tbf_ul_ass_create_rlcmac_msg(const struct gprs_rlcmac_tbf *tbf, + const struct gprs_rlcmac_pdch *pdch, + uint32_t fn); +bool tbf_ul_ass_rts(const struct gprs_rlcmac_tbf *tbf, const struct gprs_rlcmac_pdch *pdch); diff --git a/src/tbf_ul_fsm.c b/src/tbf_ul_fsm.c new file mode 100644 index 00000000..7dff3599 --- /dev/null +++ b/src/tbf_ul_fsm.c @@ -0,0 +1,365 @@ +/* tbf_ul_fsm.c + * + * Copyright (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <unistd.h> + +#include <talloc.h> + +#include <tbf_fsm.h> +#include <gprs_rlcmac.h> +#include <gprs_debug.h> +#include <gprs_ms.h> +#include <encoding.h> +#include <bts.h> + +#include <bts_pch_timer.h> + +#define X(s) (1 << (s)) + +static const struct osmo_tdef_state_timeout tbf_ul_fsm_timeouts[32] = { + [TBF_ST_NEW] = {}, + [TBF_ST_ASSIGN] = {}, + [TBF_ST_FLOW] = {}, + [TBF_ST_FINISHED] = {}, + [TBF_ST_RELEASING] = { .T = 3169 }, +}; + +/* Transition to a state, using the T timer defined in tbf_fsm_timeouts. + * The actual timeout value is in turn obtained from conn->T_defs. + * Assumes local variable fi exists. */ +#define tbf_ul_fsm_state_chg(fi, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \ + tbf_ul_fsm_timeouts, \ + tbf_ms(((struct tbf_ul_fsm_ctx *)(fi->priv))->tbf)->bts->T_defs_bts, \ + -1) + +static void mod_ass_type(struct tbf_ul_fsm_ctx *ctx, uint8_t t, bool set) +{ + const char *ch = "UNKNOWN"; + bool prev_set = ctx->state_flags & (1 << t); + + switch (t) { + case GPRS_RLCMAC_FLAG_CCCH: + ch = "CCCH"; + break; + case GPRS_RLCMAC_FLAG_PACCH: + ch = "PACCH"; + break; + default: + OSMO_ASSERT(0); + } + + LOGPTBFUL(ctx->ul_tbf, LOGL_INFO, "%sset ass. type %s [prev CCCH:%u, PACCH:%u]\n", + set ? "" : "un", ch, + !!(ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)), + !!(ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH))); + + if (set && prev_set) { + LOGPTBFUL(ctx->ul_tbf, LOGL_ERROR, + "Attempted to set ass. type %s which is already set\n", ch); + return; + } + + if (!set && !prev_set) + return; + + if (set) + ctx->state_flags |= (1 << t); + else + ctx->state_flags &= ~(1 << t); +} + + +static void st_new(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct tbf_ul_fsm_ctx *ctx = (struct tbf_ul_fsm_ctx *)fi->priv; + switch (event) { + case TBF_EV_ASSIGN_ADD_CCCH: + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_CCCH, true); + tbf_ul_fsm_state_chg(fi, TBF_ST_ASSIGN); + ul_tbf_contention_resolution_start(ctx->ul_tbf); + break; + case TBF_EV_ASSIGN_ADD_PACCH: + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_PACCH, true); + tbf_ul_fsm_state_chg(fi, TBF_ST_ASSIGN); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_assign_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct tbf_ul_fsm_ctx *ctx = (struct tbf_ul_fsm_ctx *)fi->priv; + struct GprsMs *ms = tbf_ms(ctx->tbf); + unsigned long val; + unsigned int sec, micro; + + /* If assignment for this TBF is happening on PACCH, that means the + * actual Assignment procedure (tx/rx) is happening on another TBF (eg + * Ul TBF vs DL TBF). Hence we add a security timer here to free it in + * case the other TBF doesn't succeed in informing (assigning) the MS + * about this TBF, or simply because the scheduler takes too long to + * schedule it. This timer can probably be dropped once we make the + * other TBF always signal us assignment failure (we already get + * assignment success through TBF_EV_ASSIGN_ACK_PACCH) */ + if (ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_PACCH)) { + fi->T = 3168; + val = osmo_tdef_get(ms->bts->T_defs_bts, fi->T, OSMO_TDEF_MS, -1); + val *= 4; /* 4 PKT RES REQ retransmit */ + sec = val / 1000; + micro = (val % 1000) * 1000; + LOGPTBFUL(ctx->ul_tbf, LOGL_DEBUG, + "Starting timer T3168 [UL TBF Ass (PACCH)] with %u sec. %u microsec\n", + sec, micro); + osmo_timer_schedule(&fi->timer, sec, micro); + } else if (ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)) { + /* Wait a bit for the AGCH ImmAss[PktUlAss] sent BSC->BTS to + * arrive at the MS, and for the MS to jump and starting + * listening on USFs in the assigned PDCH. + * Ideally we would first wait for TBF_EV_ASSIGN_PCUIF_CNF to + * account for queueing time, but that's only sent for data on PCH + * so far, while ImmAss for UL TBF is sent on AGCH. + */ + fi->T = -2002; + val = osmo_tdef_get(the_pcu->T_defs, fi->T, OSMO_TDEF_MS, -1); + sec = val / 1000; + micro = (val % 1000) * 1000; + LOGPTBFUL(ctx->ul_tbf, LOGL_DEBUG, + "Starting timer X2002 [assignment (AGCH)] with %u sec. %u microsec\n", + sec, micro); + osmo_timer_schedule(&fi->timer, sec, micro); + } +} + +static void st_assign(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct tbf_ul_fsm_ctx *ctx = (struct tbf_ul_fsm_ctx *)fi->priv; + + switch (event) { + case TBF_EV_ASSIGN_ADD_CCCH: + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_CCCH, true); + break; + case TBF_EV_ASSIGN_ADD_PACCH: + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_PACCH, true); + break; + case TBF_EV_ASSIGN_ACK_PACCH: + tbf_assign_control_ts(ctx->tbf); + if (ctx->state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH)) { + /* We now know that the PACCH really existed */ + LOGPTBFUL(ctx->ul_tbf, LOGL_INFO, + "The TBF has been confirmed on the PACCH, " + "changed type from CCCH to PACCH\n"); + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_CCCH, false); + mod_ass_type(ctx, GPRS_RLCMAC_FLAG_PACCH, true); + } + tbf_ul_fsm_state_chg(fi, TBF_ST_FLOW); + break; + case TBF_EV_ASSIGN_READY_CCCH: + /* change state to FLOW, so scheduler will start requesting USF */ + tbf_ul_fsm_state_chg(fi, TBF_ST_FLOW); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_flow(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct tbf_ul_fsm_ctx *ctx = (struct tbf_ul_fsm_ctx *)fi->priv; + struct GprsMs *ms = tbf_ms(ctx->tbf); + struct gprs_rlcmac_dl_tbf *dl_tbf = NULL; + + switch (event) { + case TBF_EV_FIRST_UL_DATA_RECVD: + /* TS 44.060 7a.2.1.1: "The contention resolution is completed on + * the network side when the network receives an RLC data block that + * comprises the TLLI value that identifies the mobile station and the + * TFI value associated with the TBF." */ + bts_pch_timer_stop(ms->bts, ms); + /* We may still have some DL-TBF waiting for assignment in PCH, + * which clearly won't happen since the MS is on PDCH now. Get rid + * of it, it will be re-assigned on PACCH when contention + * resolution at the MS side is done (1st UL ACK/NACK sent) */ + if ((dl_tbf = ms_dl_tbf(ms))) { + /* Get rid of previous finished UL TBF before providing a new one */ + LOGPTBFDL(dl_tbf, LOGL_NOTICE, + "Got first UL data while DL-TBF pending, killing it\n"); + tbf_free(dl_tbf_as_tbf(dl_tbf)); + dl_tbf = NULL; + } + break; + case TBF_EV_CONTENTION_RESOLUTION_MS_SUCCESS: + ul_tbf_contention_resolution_success(tbf_as_ul_tbf(ctx->tbf)); + break; + case TBF_EV_LAST_UL_DATA_RECVD: + /* All data has been sent or received, change state to FINISHED */ + tbf_ul_fsm_state_chg(fi, TBF_ST_FINISHED); + break; + case TBF_EV_MAX_N3101: + tbf_ul_fsm_state_chg(fi, TBF_ST_RELEASING); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_finished(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct tbf_ul_fsm_ctx *ctx = (struct tbf_ul_fsm_ctx *)fi->priv; + struct GprsMs *ms; + bool new_ul_tbf_requested; + + switch (event) { + case TBF_EV_CONTENTION_RESOLUTION_MS_SUCCESS: + /* UL TBF: If MS only sends 1 RLCMAC UL block, it can be that we + * end up in FINISHED state before sending the first UL ACK/NACK */ + ul_tbf_contention_resolution_success(tbf_as_ul_tbf(ctx->tbf)); + break; + case TBF_EV_FINAL_UL_ACK_CONFIRMED: + new_ul_tbf_requested = (bool)data; + /* Ref the MS, otherwise it may be freed after ul_tbf is + * detached when sending event below. */ + ms = tbf_ms(ctx->tbf); + ms_ref(ms, __func__); + /* UL TBF ACKed our transmitted UL ACK/NACK with final Ack + * Indicator set to '1'. We can free the TBF right away, the MS + * also just released its TBF on its side. */ + LOGPTBFUL(tbf_as_ul_tbf(ctx->tbf), LOGL_DEBUG, "[UPLINK] END\n"); + tbf_free(ctx->tbf); + /* Here fi, ctx and ctx->tbf are already freed! */ + /* TS 44.060 9.3.3.3.2: There might be LLC packets waiting in + * the queue but the DL TBF assignment might have been delayed + * because there was no way to reach the MS (because ul_tbf was + * in packet-active mode with FINISHED state). If MS is going + * back to packet-idle mode then we can assign the DL TBF on PCH + * now. */ + if (!new_ul_tbf_requested && ms_need_dl_tbf(ms)) + ms_new_dl_tbf_assigned_on_pch(ms); + ms_unref(ms, __func__); + break; + case TBF_EV_MAX_N3103: + tbf_ul_fsm_state_chg(fi, TBF_ST_RELEASING); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_releasing_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + /* T3169 has been set entering this state: Wait for reuse of USF and + * TFI(s) after the MS uplink assignment for this TBF is invalid. Upon + * timeout, the timer_cb does tbf_free(). + */ +} + +static void st_releasing(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + default: + OSMO_ASSERT(0); + } +} + +static int tbf_ul_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct tbf_ul_fsm_ctx *ctx = (struct tbf_ul_fsm_ctx *)fi->priv; + switch (fi->T) { + case -2002: + osmo_fsm_inst_dispatch(fi, TBF_EV_ASSIGN_READY_CCCH, NULL); + break; + case 3168: + LOGPTBFUL(ctx->ul_tbf, LOGL_NOTICE, "Releasing due to UL TBF PACCH assignment timeout\n"); + /* fall-through */ + case 3169: + tbf_free(ctx->tbf); + break; + default: + OSMO_ASSERT(0); + } + return 0; +} + +static struct osmo_fsm_state tbf_ul_fsm_states[] = { + [TBF_ST_NEW] = { + .in_event_mask = + X(TBF_EV_ASSIGN_ADD_CCCH) | + X(TBF_EV_ASSIGN_ADD_PACCH), + .out_state_mask = + X(TBF_ST_ASSIGN) | + X(TBF_ST_FLOW), + .name = "NEW", + .action = st_new, + }, + [TBF_ST_ASSIGN] = { + .in_event_mask = + X(TBF_EV_ASSIGN_ADD_CCCH) | + X(TBF_EV_ASSIGN_ADD_PACCH) | + X(TBF_EV_ASSIGN_ACK_PACCH) | + X(TBF_EV_ASSIGN_READY_CCCH), + .out_state_mask = + X(TBF_ST_FLOW) | + X(TBF_ST_FINISHED), + .name = "ASSIGN", + .action = st_assign, + .onenter = st_assign_on_enter, + }, + [TBF_ST_FLOW] = { + .in_event_mask = + X(TBF_EV_FIRST_UL_DATA_RECVD) | + X(TBF_EV_CONTENTION_RESOLUTION_MS_SUCCESS) | + X(TBF_EV_LAST_UL_DATA_RECVD) | + X(TBF_EV_MAX_N3101), + .out_state_mask = + X(TBF_ST_ASSIGN) | + X(TBF_ST_FINISHED) | + X(TBF_ST_RELEASING), + .name = "FLOW", + .action = st_flow, + }, + [TBF_ST_FINISHED] = { + .in_event_mask = + X(TBF_EV_CONTENTION_RESOLUTION_MS_SUCCESS) | + X(TBF_EV_FINAL_UL_ACK_CONFIRMED) | + X(TBF_EV_MAX_N3103), + .out_state_mask = + X(TBF_ST_RELEASING), + .name = "FINISHED", + .action = st_finished, + }, + [TBF_ST_RELEASING] = { + .in_event_mask = 0, + .out_state_mask = 0, + .name = "RELEASING", + .action = st_releasing, + .onenter = st_releasing_on_enter, + }, +}; + +struct osmo_fsm tbf_ul_fsm = { + .name = "UL_TBF", + .states = tbf_ul_fsm_states, + .num_states = ARRAY_SIZE(tbf_ul_fsm_states), + .timer_cb = tbf_ul_fsm_timer_cb, + .log_subsys = DTBFUL, + .event_names = tbf_fsm_event_names, +}; + +static __attribute__((constructor)) void tbf_ul_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&tbf_ul_fsm) == 0); +} diff --git a/src/wireshark_compat.h b/src/wireshark_compat.h index 13b165a0..2840124f 100644 --- a/src/wireshark_compat.h +++ b/src/wireshark_compat.h @@ -10,10 +10,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This header contains a few definitions required by rlcmac and csn1 files |