aboutsummaryrefslogtreecommitdiffstats
path: root/src/alloc_algo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/alloc_algo.cpp')
-rw-r--r--src/alloc_algo.cpp904
1 files changed, 904 insertions, 0 deletions
diff --git a/src/alloc_algo.cpp b/src/alloc_algo.cpp
new file mode 100644
index 00000000..11789c34
--- /dev/null
+++ b/src/alloc_algo.cpp
@@ -0,0 +1,904 @@
+/* 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
+ * 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_rlcmac.h>
+#include <gprs_debug.h>
+#include <bts.h>
+#include <tbf.h>
+#include <tbf_ul.h>
+#include <pdch.h>
+#include <gprs_ms.h>
+#include <pcu_utils.h>
+
+#include <errno.h>
+#include <values.h>
+
+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>
+}
+
+/* Consider a PDCH as idle if has at most this number of TBFs assigned to it */
+#define PDCH_IDLE_TBF_THRESH 1
+
+#define LOGPSL(req, level, fmt, args...) LOGP(DRLCMAC, level, "[%s] " fmt, \
+ (req->direction == GPRS_RLCMAC_DL_TBF) ? "DL" : "UL", ## args)
+
+#define LOGPAL(req, kind, level, fmt, args...) LOGPSL(req, level, \
+ "algo %s <%s> (suggested TRX: %d): " fmt, \
+ 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)
+{
+ int i;
+
+ for (i = 0; i < 8; i += 1, val = val >> 1) {
+ if (val & 1)
+ buf[i] = set_char;
+ else if (unset_char)
+ buf[i] = unset_char;
+ }
+
+ return buf;
+}
+
+static uint8_t find_possible_pdchs(const struct gprs_rlcmac_trx *trx, uint8_t max_slots, uint8_t mask,
+ const char *mask_reason = NULL)
+{
+ unsigned ts;
+ uint8_t valid_ts_set = 0;
+ int8_t last_tsc = -1; /* must be signed */
+
+ for (ts = 0; ts < ARRAY_SIZE(trx->pdch); ts++) {
+ const struct gprs_rlcmac_pdch *pdch;
+
+ pdch = &trx->pdch[ts];
+ if (!pdch->is_enabled()) {
+ LOGP(DRLCMAC, LOGL_DEBUG, "- Skipping TS %d, because "
+ "not enabled\n", ts);
+ continue;
+ }
+
+ if (((1 << ts) & mask) == 0) {
+ if (mask_reason)
+ LOGP(DRLCMAC, LOGL_DEBUG,
+ "- Skipping TS %d, because %s\n",
+ ts, mask_reason);
+ continue;
+ }
+
+ if (max_slots > 1) {
+ /* check if TSC changes, see TS 45.002, 6.4.2 */
+ if (last_tsc < 0)
+ last_tsc = pdch->tsc;
+ else if (last_tsc != pdch->tsc) {
+ LOGP(DRLCMAC, LOGL_ERROR,
+ "Skipping TS %d of TRX=%d, because it "
+ "has different TSC than lower TS of TRX. "
+ "In order to allow multislot, all "
+ "slots must be configured with the same "
+ "TSC!\n", ts, trx->trx_no);
+ continue;
+ }
+ }
+
+ valid_ts_set |= 1 << ts;
+ }
+
+ return valid_ts_set;
+}
+
+static int compute_usage_by_num_tbfs(const struct gprs_rlcmac_pdch *pdch, enum gprs_rlcmac_tbf_direction dir)
+{
+ return pdch->num_tbfs(dir);
+}
+
+static int compute_usage_by_reservation(const struct gprs_rlcmac_pdch *pdch, enum gprs_rlcmac_tbf_direction)
+{
+ return
+ pdch->num_reserved(GPRS_RLCMAC_DL_TBF) +
+ pdch->num_reserved(GPRS_RLCMAC_UL_TBF);
+}
+
+static int compute_usage_for_algo_a(const struct gprs_rlcmac_pdch *pdch, enum gprs_rlcmac_tbf_direction dir)
+{
+ int usage =
+ pdch->num_tbfs(GPRS_RLCMAC_DL_TBF) +
+ pdch->num_tbfs(GPRS_RLCMAC_UL_TBF) +
+ compute_usage_by_reservation(pdch, dir);
+
+ if (pdch->assigned_tfi(reverse(dir)) == NO_FREE_TFI)
+ /* No TFI in the opposite direction, avoid it */
+ usage += 32;
+
+ return usage;
+
+}
+
+/*! Return the TS which corresponds to least busy PDCH
+ *
+ * \param[in] trx Pointer to TRX object
+ * \param[in] dir TBF direction
+ * \param[in] mask set of available timeslots
+ * \param[in] fn Function pointer to function which computes number of associated TBFs
+ * \param[out] free_tfi Free TFI
+ * \param[out] free_usf Free USF
+ * \returns TS number or -1 if unable to find
+ */
+static int find_least_busy_pdch(const struct gprs_rlcmac_trx *trx, enum gprs_rlcmac_tbf_direction dir, uint8_t mask,
+ int (*fn)(const struct gprs_rlcmac_pdch *, enum gprs_rlcmac_tbf_direction dir),
+ int *free_tfi = NULL, int *free_usf = NULL)
+{
+ unsigned ts;
+ int min_used = INT_MAX;
+ int min_ts = -1;
+ int min_tfi = -1;
+ int min_usf = -1;
+
+ for (ts = 0; ts < ARRAY_SIZE(trx->pdch); ts++) {
+ const struct gprs_rlcmac_pdch *pdch = &trx->pdch[ts];
+ int num_tbfs;
+ int usf = -1; /* must be signed */
+ int tfi = -1;
+
+ if (((1 << ts) & mask) == 0)
+ continue;
+
+ num_tbfs = fn(pdch, dir);
+
+ if (num_tbfs < min_used) {
+ /* We have found a candidate */
+ /* Make sure that a TFI is available */
+ if (free_tfi) {
+ tfi = find_free_tfi(pdch->assigned_tfi(dir));
+ if (tfi < 0) {
+ LOGP(DRLCMAC, LOGL_DEBUG,
+ "- Skipping TS %d, because "
+ "no TFI available\n", ts);
+ continue;
+ }
+ }
+ /* Make sure that an USF is available */
+ if (dir == GPRS_RLCMAC_UL_TBF) {
+ usf = find_free_usf(pdch->assigned_usf());
+ if (usf < 0) {
+ LOGP(DRLCMAC, LOGL_DEBUG,
+ "- Skipping TS %d, because "
+ "no USF available\n", ts);
+ continue;
+ }
+ }
+ if (min_ts >= 0)
+ LOGP(DRLCMAC, LOGL_DEBUG,
+ "- Skipping TS %d, because "
+ "num TBFs %d > %d\n",
+ min_ts, min_used, num_tbfs);
+ min_used = num_tbfs;
+ min_ts = ts;
+ min_tfi = tfi;
+ min_usf = usf;
+ } else {
+ LOGP(DRLCMAC, LOGL_DEBUG,
+ "- Skipping TS %d, because "
+ "num TBFs %d >= %d\n",
+ ts, num_tbfs, min_used);
+ }
+ }
+
+ if (min_ts < 0)
+ return -1;
+
+ if (free_tfi)
+ *free_tfi = min_tfi;
+ if (free_usf)
+ *free_usf = min_usf;
+
+ return min_ts;
+}
+
+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 (req->ms && ms_current_trx(req->ms))
+ return ms_current_trx(req->ms)->trx_no;
+
+ 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(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())
+ continue;
+
+ if (pdch->assigned_tfi(GPRS_RLCMAC_UL_TBF) == NO_FREE_TFI)
+ continue;
+
+ if (pdch->assigned_tfi(GPRS_RLCMAC_DL_TBF) == NO_FREE_TFI)
+ continue;
+
+ return trx_no;
+ }
+ }
+
+ return -EBUSY;
+}
+
+static bool idle_pdch_avail(const struct gprs_rlcmac_bts *bts)
+{
+ unsigned trx_no;
+ unsigned ts;
+
+ /* Find the first PDCH with an unused DL TS */
+ for (trx_no = 0; trx_no < ARRAY_SIZE(bts->trx); trx_no += 1) {
+ const struct gprs_rlcmac_trx *trx = &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())
+ continue;
+
+ if (pdch->num_tbfs(GPRS_RLCMAC_DL_TBF) > PDCH_IDLE_TBF_THRESH)
+ continue;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*! Return free TFI
+ *
+ * \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 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 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",
+ req->use_trx, trx->trx_no);
+ return -EINVAL;
+ }
+ use_trx = trx->trx_no;
+ }
+
+ tfi = bts_tfi_find_free(req->bts, req->direction, &trx_no, use_trx);
+ if (tfi < 0)
+ return -EBUSY;
+
+ if (trx_no_)
+ *trx_no_ = trx_no;
+
+ return tfi;
+}
+
+/*! Slot Allocation: Algorithm A
+ *
+ * Assign single slot for uplink and downlink
+ *
+ * \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(const struct alloc_resources_req *req,
+ struct alloc_resources_res *res)
+{
+ int ts = -1;
+ uint8_t ul_slots, dl_slots;
+ int trx_no;
+ int tfi = -1;
+ int usf = -1;
+ uint8_t mask = 0xff;
+ const char *mask_reason = NULL;
+ gprs_rlcmac_trx *trx = ms_current_trx(req->ms);
+ struct gprs_rlcmac_pdch *first_common_ts = ms_first_common_ts(req->ms);
+
+ LOGPAL(req, "A", LOGL_DEBUG, "Alloc start\n");
+
+ trx_no = find_trx(req);
+ if (trx_no < 0) {
+ LOGPAL(req, "A", LOGL_NOTICE,
+ "failed to find a usable TRX (TFI exhausted)\n");
+ return trx_no;
+ }
+ if (!trx)
+ trx = &req->bts->trx[trx_no];
+
+ dl_slots = ms_reserved_dl_slots(req->ms);
+ ul_slots = ms_reserved_ul_slots(req->ms);
+
+ if (first_common_ts) {
+ mask_reason = "need to reuse 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;
+ }
+
+ mask = find_possible_pdchs(trx, 1, mask, mask_reason);
+ if (!mask)
+ return -EINVAL;
+
+ ts = find_least_busy_pdch(trx, req->direction, mask,
+ compute_usage_for_algo_a,
+ &tfi, &usf);
+
+ 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(req, "A", LOGL_NOTICE,
+ "failed to allocate a TS, no TFI available\n");
+ return -EBUSY;
+ }
+
+ 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;
+
+ bts_do_rate_ctr_inc(req->bts, CTR_TBF_ALLOC_ALGO_A);
+ return 0;
+}
+
+/*! Compute capacity of a given TRX
+ *
+ * \param[in] trx Pointer to TRX object
+ * \param[in] rx_window Receive window
+ * \param[in] tx_window Transmit window
+ * \returns non-negative capacity
+ */
+static inline unsigned compute_capacity(const struct gprs_rlcmac_trx *trx, int rx_window, int tx_window)
+{
+ const struct gprs_rlcmac_pdch *pdch;
+ unsigned ts, capacity = 0;
+
+ for (ts = 0; ts < ARRAY_SIZE(trx->pdch); ts++) {
+ pdch = &trx->pdch[ts];
+ if (rx_window & (1 << ts))
+ capacity += OSMO_MAX(32 - pdch->num_reserved(GPRS_RLCMAC_DL_TBF), 1);
+
+ /* Only consider common slots for UL */
+ if (tx_window & rx_window & (1 << ts)) {
+ if (find_free_usf(pdch->assigned_usf()) >= 0)
+ capacity += OSMO_MAX(32 - pdch->num_reserved(GPRS_RLCMAC_UL_TBF), 1);
+ }
+ }
+
+ return capacity;
+}
+
+/*! Decide if a given slot should be skipped by multislot allocator
+ *
+ * \param[in] ms_class Pointer to MS Class object
+ * \param[in] check_tr Flag indicating whether we should check for Tra or Tta parameters for a given MS class
+ * \param[in] rx_window Receive window
+ * \param[in] tx_window Transmit window
+ * \param[in,out] checked_rx array with already checked RX timeslots
+ * \returns true if the slot should be skipped, false otherwise
+ */
+static bool skip_slot(uint8_t mslot_class, bool check_tr,
+ int16_t rx_window, int16_t tx_window,
+ uint32_t *checked_rx)
+{
+ uint8_t common_slot_count, req_common_slots,
+ rx_slot_count = pcu_bitcount(rx_window),
+ tx_slot_count = pcu_bitcount(tx_window);
+
+ /* Check compliance with TS 45.002, table 6.4.2.2.1 */
+ /* Whether to skip this round doesn not only depend on the bit
+ * sets but also on check_tr. Therefore this check must be done
+ * before doing the mslot_test_and_set_bit shortcut. */
+ if (mslot_class_get_type(mslot_class) == 1) {
+ uint16_t slot_sum = rx_slot_count + tx_slot_count;
+ /* Assume down + up / dynamic.
+ * TODO: For ext-dynamic, down only, up only add more cases.
+ */
+ if (slot_sum <= 6 && tx_slot_count < 3) {
+ if (!check_tr)
+ return true; /* Skip Tta */
+ } else if (slot_sum > 6 && tx_slot_count < 3) {
+ if (check_tr)
+ return true; /* Skip Tra */
+ } else
+ return true; /* No supported row in TS 45.002, table 6.4.2.2.1. */
+ }
+
+ /* Avoid repeated RX combination check */
+ if (mslot_test_and_set_bit(checked_rx, rx_window))
+ return true;
+
+ /* Check number of common slots according to TS 45.002, ยง6.4.2.2 */
+ common_slot_count = pcu_bitcount(tx_window & rx_window);
+ req_common_slots = OSMO_MIN(tx_slot_count, rx_slot_count);
+ if (mslot_class_get_type(mslot_class) == 1)
+ req_common_slots = OSMO_MIN(req_common_slots, 2);
+
+ if (req_common_slots != common_slot_count)
+ return true;
+
+ return false;
+}
+
+/*! Find set of slots available for allocation while taking MS class into account
+ *
+ * \param[in] trx Pointer to TRX object
+ * \param[in] mslot_class The multislot class
+ * \param[in,out] ul_slots set of UL timeslots
+ * \param[in,out] dl_slots set of DL timeslots
+ * \returns negative error code or 0 on success
+ */
+int find_multi_slots(struct gprs_rlcmac_trx *trx, uint8_t mslot_class, uint8_t *ul_slots, uint8_t *dl_slots)
+{
+ const uint8_t Rx = mslot_class_get_rx(mslot_class), /* Max number of Rx slots */
+ Tx = mslot_class_get_tx(mslot_class), /* Max number of Tx slots */
+ Sum = mslot_class_get_sum(mslot_class), /* Max number of Tx + Rx slots */
+ Type = mslot_class_get_type(mslot_class);
+ uint8_t max_slots, num_rx, num_tx, mask_sel, pdch_slots, ul_ts, dl_ts;
+ int16_t rx_window, tx_window;
+ char slot_info[9] = {0};
+ int max_capacity = -1;
+ uint8_t max_ul_slots = 0, max_dl_slots = 0;
+
+ if (mslot_class)
+ LOGP(DRLCMAC, LOGL_DEBUG, "Slot Allocation (Algorithm B) for class %d\n",
+ mslot_class);
+
+ if (Tx == MS_NA) {
+ LOGP(DRLCMAC, LOGL_NOTICE, "Multislot class %d not applicable.\n",
+ mslot_class);
+ return -EINVAL;
+ }
+
+ max_slots = OSMO_MAX(Rx, Tx);
+
+ if (*dl_slots == 0)
+ *dl_slots = 0xff;
+
+ if (*ul_slots == 0)
+ *ul_slots = 0xff;
+
+ pdch_slots = find_possible_pdchs(trx, max_slots, 0xff);
+
+ *dl_slots &= pdch_slots;
+ *ul_slots &= pdch_slots;
+
+ LOGP(DRLCMAC, LOGL_DEBUG, "- Possible DL/UL slots: (TS=0)\"%s\"(TS=7)\n",
+ set_flag_chars(set_flag_chars(set_flag_chars(slot_info,
+ *dl_slots, 'D', '.'),
+ *ul_slots, 'U'),
+ *ul_slots & *dl_slots, 'C'));
+
+ /* Check for each UL (TX) slot */
+
+ /* Iterate through possible numbers of TX slots */
+ for (num_tx = 1; num_tx <= Tx; num_tx += 1) {
+ uint16_t tx_valid_win = (1 << num_tx) - 1;
+ uint8_t rx_mask[MASK_TR + 1];
+
+ mslot_fill_rx_mask(mslot_class, num_tx, rx_mask);
+
+ /* Rotate group of TX slots: UUU-----, -UUU----, ..., UU-----U */
+ for (ul_ts = 0; ul_ts < 8; ul_ts += 1, tx_valid_win <<= 1) {
+ uint16_t rx_valid_win;
+ uint32_t checked_rx[256/32] = {0};
+
+ /* Wrap valid window */
+ tx_valid_win = mslot_wrap_window(tx_valid_win);
+
+ /* for multislot type 1: don't split the window to wrap around.
+ * E.g. 'UU-----U' is invalid for a 4 TN window. Except 8 TN window.
+ * See 45.002 B.1 */
+ if (Type == 1 && num_tx < 8 &&
+ tx_valid_win & (1 << 0) && tx_valid_win & (1 << 7))
+ continue;
+
+ tx_window = tx_valid_win;
+
+ /* Filter out unavailable slots */
+ tx_window &= *ul_slots;
+
+ /* Skip if the the first TS (ul_ts) is not in the set */
+ if ((tx_window & (1 << ul_ts)) == 0)
+ continue;
+
+ /* Skip if the the last TS (ul_ts+num_tx-1) is not in the set */
+ if ((tx_window & (1 << ((ul_ts+num_tx-1) % 8))) == 0)
+ continue;
+
+ num_rx = OSMO_MIN(Rx, Sum - num_tx);
+ rx_valid_win = (1 << num_rx) - 1;
+
+ /* Rotate group of RX slots: DDD-----, -DDD----, ..., DD-----D */
+ for (dl_ts = 0; dl_ts < 8; dl_ts += 1, rx_valid_win <<= 1) {
+ /* Wrap valid window */
+ rx_valid_win = (rx_valid_win | rx_valid_win >> 8) & 0xff;
+
+ /* for multislot type 1: don't split the window to wrap around.
+ * E.g. 'DD-----D' is invalid for a 4 TN window. Except 8 TN window.
+ * See 45.002 B.1 */
+ if (Type == 1 && num_rx < 8 &&
+ (rx_valid_win & (1 << 0)) && (rx_valid_win & (1 << 7)))
+ continue;
+
+ /* Validate with both Tta/Ttb/Trb and Ttb/Tra/Trb */
+ for (mask_sel = MASK_TT; mask_sel <= MASK_TR; mask_sel += 1) {
+ int capacity;
+
+ rx_window = mslot_filter_bad(rx_mask[mask_sel], ul_ts, *dl_slots, rx_valid_win);
+ if (rx_window < 0)
+ continue;
+
+ if (skip_slot(mslot_class, mask_sel != MASK_TT, rx_window, tx_window, checked_rx))
+ continue;
+
+ /* Compute capacity */
+ capacity = compute_capacity(trx, rx_window, tx_window);
+
+#ifdef ENABLE_TS_ALLOC_DEBUG
+ LOGP(DRLCMAC, LOGL_DEBUG,
+ "- Considering DL/UL slots: (TS=0)\"%s\"(TS=7), "
+ "capacity = %d\n",
+ set_flag_chars(set_flag_chars(set_flag_chars(set_flag_chars(
+ slot_info,
+ rx_bad, 'x', '.'),
+ rx_window, 'D'),
+ tx_window, 'U'),
+ rx_window & tx_window, 'C'),
+ capacity);
+#endif
+
+ if (capacity <= max_capacity)
+ continue;
+
+ max_capacity = capacity;
+ max_ul_slots = tx_window;
+ max_dl_slots = rx_window;
+ }
+ }
+ }
+ }
+
+ if (!max_ul_slots || !max_dl_slots) {
+ LOGP(DRLCMAC, LOGL_NOTICE,
+ "No valid UL/DL slot combination found\n");
+ bts_do_rate_ctr_inc(trx->bts, CTR_TBF_ALLOC_FAIL_NO_SLOT_COMBI);
+ return -EINVAL;
+ }
+
+ *ul_slots = max_ul_slots;
+ *dl_slots = max_dl_slots;
+
+ return 0;
+}
+
+/*! Count used bits in slots and reserved_slots bitmasks
+ *
+ * \param[in] slots Timeslots in use
+ * \param[in] reserved_slots Reserved timeslots
+ * \param[out] slotcount Number of TS in use
+ * \param[out] reserve_count Number of reserved TS
+ */
+static void count_slots(uint8_t slots, uint8_t reserved_slots, uint8_t *slotcount, uint8_t *reserve_count)
+{
+ (*slotcount) = pcu_bitcount(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] 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, 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, direction, ret, compute_usage_by_num_tbfs, NULL, NULL);
+
+ if (ts < 0)
+ return ffs(ret);
+
+ return ret & (1 << ts);
+}
+
+/*! Find set of timeslots available for allocation
+ *
+ * \param[in] req Contains all the requested params
+ * \param[in] trx Pointer to TRX object
+ * \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
+ * \param[in] reserved_dl_slots set of reserved DL timeslots
+ * \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 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 = req->direction == GPRS_RLCMAC_UL_TBF;
+ uint8_t sl = is_ul ? ul_slots : dl_slots;
+ char slot_info[9] = { 0 };
+
+ 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",
+ is_ul ? "uplink" : "downlink");
+ bts_do_rate_ctr_inc(trx->bts, CTR_TBF_ALLOC_FAIL_NO_SLOT_AVAIL);
+ return -EINVAL;
+ }
+
+ if (is_ul) {
+ snprintf(slot_info, 9, OSMO_BIT_SPEC, OSMO_BIT_PRINT_EX(reserved_ul_slots, 'u'));
+ masked_override_with(slot_info, sl, 'U');
+ } else {
+ snprintf(slot_info, 9, OSMO_BIT_SPEC, OSMO_BIT_PRINT_EX(reserved_dl_slots, 'd'));
+ masked_override_with(slot_info, sl, 'D');
+ }
+
+ LOGPC(DRLCMAC, LOGL_DEBUG, "Selected %s slots: (TS=0)\"%s\"(TS=7), %s\n",
+ is_ul ? "UL" : "DL",
+ slot_info, req->single ? "single" : "multi");
+
+ return sl;
+}
+
+/*! Allocate USF according to a given UL TS mapping
+ *
+ * \param[in] trx Pointer to TRX object
+ * \param[in] selected_ul_slots set of UL timeslots selected for allocation
+ * \param[in] dl_slots set of DL timeslots
+ * \param[out] usf array for allocated USF
+ * \returns updated UL TS mask or negative on error
+ */
+static int allocate_usf(const gprs_rlcmac_trx *trx, uint8_t selected_ul_slots, uint8_t dl_slots,
+ int *usf_list)
+{
+ uint8_t ul_slots = selected_ul_slots & dl_slots;
+ unsigned int ts;
+
+ for (ts = 0; ts < ARRAY_SIZE(trx->pdch); ts++) {
+ const struct gprs_rlcmac_pdch *pdch = &trx->pdch[ts];
+ int8_t free_usf;
+
+ if (((1 << ts) & ul_slots) == 0)
+ continue;
+
+ free_usf = find_free_usf(pdch->assigned_usf());
+ if (free_usf < 0) {
+ LOGP(DRLCMAC, LOGL_DEBUG,
+ "- Skipping TS %d, because "
+ "no USF available\n", ts);
+ ul_slots &= (~(1 << ts)) & 0xff;
+ continue;
+ }
+ usf_list[ts] = free_usf;
+ }
+
+ if (!ul_slots) {
+ LOGP(DRLCMAC, LOGL_NOTICE, "No USF available\n");
+ bts_do_rate_ctr_inc(trx->bts, CTR_TBF_ALLOC_FAIL_NO_USF);
+ return -EBUSY;
+ }
+
+ return ul_slots;
+}
+
+/*! Slot Allocation: Algorithm B
+ *
+ * Assign as many downlink slots as possible.
+ * Assign one uplink slot. (With free USF)
+ *
+ * \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(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_tn;
+ uint8_t slotcount = 0;
+ uint8_t reserve_count = 0, trx_no;
+ int first_ts;
+ int rc;
+ int tfi;
+ 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(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(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(req, &trx_no);
+ if (tfi < 0) {
+ LOGPAL(req, "B", LOGL_NOTICE, "failed to allocate a TFI\n");
+ return tfi;
+ }
+
+ /* Step 2b: Reserve slots on the TRX for the MS */
+ trx = &req->bts->trx[trx_no];
+
+ if (!reserved_dl_slots || !reserved_ul_slots) {
+ rc = find_multi_slots(trx, ms_ms_class(req->ms), &reserved_ul_slots, &reserved_dl_slots);
+ if (rc < 0)
+ return rc;
+ }
+ dl_slots = reserved_dl_slots;
+ ul_slots = reserved_ul_slots;
+
+ /* Step 3a: Derive the slot set for the current TBF */
+ 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 (req->direction == GPRS_RLCMAC_DL_TBF) {
+ dl_slots = rc;
+ count_slots(dl_slots, reserved_dl_slots, &slotcount, &reserve_count);
+ } else {
+ rc = allocate_usf(trx, rc, dl_slots, &res->usf[0]);
+ if (rc < 0)
+ return rc;
+
+ ul_slots = rc;
+ reserved_ul_slots = ul_slots;
+
+ count_slots(ul_slots, reserved_ul_slots, &slotcount, &reserve_count);
+ }
+
+ first_ts = ffs(rc) - 1;
+ if (first_ts < 0) {
+ LOGPAL(req, "B", LOGL_NOTICE, "first slot unavailable\n");
+ return -EINVAL;
+ }
+
+ 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;
+ }
+ 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 {
+ res->upgrade_to_multislot = false;
+ LOGPAL(req, "B", LOGL_INFO, "using %d slots\n", slotcount);
+ }
+
+ 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 (req->direction == GPRS_RLCMAC_DL_TBF)
+ res->ass_slots_mask = dl_slots;
+ else
+ res->ass_slots_mask = ul_slots;
+
+ bts_do_rate_ctr_inc(req->bts, CTR_TBF_ALLOC_ALGO_B);
+
+ return 0;
+}
+
+/*! Slot Allocation: Algorithm dynamic
+ *
+ * This meta algorithm automatically selects on of the other algorithms based
+ * on the current system state.
+ *
+ * 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] 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(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 (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 (!req->bts->multislot_disabled) {
+ rc = alloc_algorithm_b(req, res);
+ if (rc >= 0)
+ return rc;
+
+ if (!req->bts->multislot_disabled)
+ LOGP(DRLCMAC, LOGL_DEBUG, "Disabling algorithm B\n");
+ req->bts->multislot_disabled = 1;
+ }
+
+ return alloc_algorithm_a(req, res);
+}
+
+int gprs_alloc_max_dl_slots_per_ms(const struct gprs_rlcmac_bts *bts, uint8_t ms_class)
+{
+ int rx = mslot_class_get_rx(ms_class);
+
+ if (rx == MS_NA)
+ rx = 4;
+
+ if (the_pcu->alloc_algorithm == alloc_algorithm_a)
+ return 1;
+
+ if (bts->multislot_disabled)
+ return 1;
+
+ return rx;
+}