aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/cbch_scheduler.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc/cbch_scheduler.c')
-rw-r--r--src/osmo-bsc/cbch_scheduler.c287
1 files changed, 287 insertions, 0 deletions
diff --git a/src/osmo-bsc/cbch_scheduler.c b/src/osmo-bsc/cbch_scheduler.c
new file mode 100644
index 000000000..ef93b7a4a
--- /dev/null
+++ b/src/osmo-bsc/cbch_scheduler.c
@@ -0,0 +1,287 @@
+/* CBCH (Cell Broadcast Channel) Scheduler for OsmoBSC */
+/*
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/stats.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/abis_rsl.h>
+
+/* add all pages of given SMSCB so they appear as soon as possible *after* (included) base_idx. */
+static int bts_smscb_sched_add_after(struct bts_smscb_page **sched_arr, int sched_arr_size,
+ int base_idx, struct bts_smscb_message *smscb)
+{
+ int arr_idx = base_idx;
+ int i;
+
+ OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page));
+ for (i = 0; i < smscb->num_pages; i++) {
+ while (sched_arr[arr_idx]) {
+ arr_idx++;
+ if (arr_idx >= sched_arr_size)
+ return -ENOSPC;
+ }
+ sched_arr[arr_idx] = &smscb->page[i];
+ }
+ return arr_idx;
+}
+
+/* add all pages of given smscb so they appear *before* (included) last_idx. */
+static int bts_smscb_sched_add_before(struct bts_smscb_page **sched_arr, int sched_arr_size,
+ int last_idx, struct bts_smscb_message *smscb)
+{
+ int arr_idx = last_idx;
+ int last_used_idx = 0;
+ int i;
+
+ OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page));
+ OSMO_ASSERT(smscb->num_pages >= 1);
+
+ for (i = smscb->num_pages - 1; i >= 0; i--) {
+ while (sched_arr[arr_idx]) {
+ arr_idx--;
+ if (arr_idx < 0)
+ return -ENOSPC;
+ }
+ sched_arr[arr_idx] = &smscb->page[i];
+ if (i == smscb->num_pages)
+ last_used_idx = i;
+ }
+ return last_used_idx;
+}
+
+/* obtain the least frequently scheduled SMSCB for given SMSCB channel */
+static struct bts_smscb_message *
+bts_smscb_chan_get_least_frequent_smscb(struct bts_smscb_chan_state *cstate)
+{
+ if (llist_empty(&cstate->messages))
+ return NULL;
+ /* messages are expected to be ordered with increasing period, so we're
+ * able to return the last message in the list */
+ return llist_entry(cstate->messages.prev, struct bts_smscb_message, list);
+}
+
+/*! Generate per-BTS SMSCB scheduling array
+ * \param[in] cstate BTS CBCH channel state
+ * \param[out] arr_out return argument for allocated + generated scheduling array
+ * \return size of returned scheduling array arr_out in number of entries; negative on error */
+int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smscb_page ***arr_out)
+{
+ struct bts_smscb_message *smscb, *least_freq;
+ struct bts_smscb_page **arr;
+ int arr_size;
+ int rc;
+
+ /* start with one instance of the least frequent message at position 0, as we
+ * need to transmit it exactly once during the duration of the scheduling array */
+ least_freq = bts_smscb_chan_get_least_frequent_smscb(cstate);
+ if (!least_freq) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_DEBUG, "No SMSCB; cannot create schedule array\n");
+ *arr_out = NULL;
+ return 0;
+ }
+ arr_size = least_freq->input.rep_period;
+ arr = talloc_zero_array(cstate->bts, struct bts_smscb_page *, arr_size);
+ OSMO_ASSERT(arr);
+ rc = bts_smscb_sched_add_after(arr, arr_size, 0, least_freq);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of "
+ "very first SMSCB %s ?!?\n", bts_smscb_msg2str(least_freq));
+ talloc_free(arr);
+ return rc;
+ }
+
+ /* continue filling with repetitions of the more frequent messages, starting from
+ * the most frequent message to the least frequent one, repeating them as needed
+ * throughout the duration of the array */
+ llist_for_each_entry(smscb, &cstate->messages, list) {
+ int last_page;
+ if (smscb == least_freq)
+ continue;
+ /* messages are expected to be ordered with increasing period, so we're
+ * starting with the most frequent / shortest period first */
+ rc = bts_smscb_sched_add_after(arr, arr_size, 0, smscb);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of "
+ "SMSCB %s\n", bts_smscb_msg2str(smscb));
+ talloc_free(arr);
+ return rc;
+ }
+ last_page = rc;
+
+ while (last_page < cstate->sched_arr_size) {
+ /* store further instances in a way that the last block of the N+1th instance
+ * happens no later than "interval" after the last block of the Nth instance */
+ rc = bts_smscb_sched_add_before(arr, arr_size,
+ last_page + smscb->input.rep_period, smscb);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule further "
+ "SMSCB %s\n", bts_smscb_msg2str(smscb));
+ talloc_free(arr);
+ return rc;
+ }
+ last_page = rc;
+ }
+ }
+ *arr_out = arr;
+ return arr_size;
+}
+
+/*! Pull the next to-be-transmitted SMSCB page out of the scheduler for the given channel */
+struct bts_smscb_page *bts_smscb_pull_page(struct bts_smscb_chan_state *cstate)
+{
+ struct bts_smscb_page *page;
+
+ /* if there are no messages to schedule, there is no array */
+ if (!cstate->sched_arr)
+ return NULL;
+
+ /* obtain the page from the scheduler array */
+ page = cstate->sched_arr[cstate->next_idx];
+
+ /* increment the index for the next call to this function */
+ cstate->next_idx = (cstate->next_idx + 1) % cstate->sched_arr_size;
+
+ /* the array can have gaps in between where there is nothing scheduled */
+ if (!page)
+ return NULL;
+
+ return page;
+}
+
+/*! To be called after bts_smscb_pull_page() in order to update transmission count and
+ * check if SMSCB is complete.
+ * \param[in] cstate BTS CBC channel state
+ * \param[in] page SMSCB Page which had been returned by bts_smscb_pull_page() and which
+ * is no longer needed now */
+void bts_smscb_page_done(struct bts_smscb_chan_state *cstate, struct bts_smscb_page *page)
+{
+ struct bts_smscb_message *smscb = page->msg;
+
+ /* If this is the last page of a SMSCB, increment the SMSCB number-of-xmit counter */
+ if (page->nr == smscb->num_pages) {
+ smscb->bcast_count++;
+ /* Check if the SMSCB transmission duration is now over */
+ if (smscb->bcast_count >= smscb->input.num_bcast_req)
+ bts_smscb_del(smscb, cstate, "COMPLETE");
+ }
+}
+
+
+/***********************************************************************
+ * BTS / RSL side
+ ***********************************************************************/
+
+static void bts_cbch_send_one(struct bts_smscb_chan_state *cstate)
+{
+ struct bts_smscb_page *page;
+ struct gsm_bts *bts = cstate->bts;
+ struct rsl_ie_cb_cmd_type cb_cmd;
+ bool is_extended = false;
+
+ if (cstate == &bts->cbch_extended)
+ is_extended = true;
+
+ if (cstate->overflow) {
+ LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB due to overflow (%u)\n",
+ cstate->overflow);
+ cstate->overflow--;
+ return;
+ }
+
+ page = bts_smscb_pull_page(cstate);
+ if (!page) {
+ LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB: No page available\n");
+ return;
+ }
+
+ cb_cmd.spare = 0;
+ cb_cmd.def_bcast = 0;
+ cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL;
+ switch (page->num_blocks) {
+ case 1:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1;
+ break;
+ case 2:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2;
+ break;
+ case 3:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3;
+ break;
+ case 4:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4;
+ break;
+ default:
+ osmo_panic("SMSCB Page must have 1..4 blocks, not %d\n", page->num_blocks);
+ }
+ rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, is_extended,
+ page->data, sizeof(page->data));
+
+ bts_smscb_page_done(cstate, page);
+}
+
+static void bts_cbch_timer(void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)data;
+
+ bts_cbch_send_one(&bts->cbch_basic);
+ bts_cbch_send_one(&bts->cbch_extended);
+
+ bts_cbch_timer_schedule(bts);
+}
+
+/* There is one SMSCB message (page) per eight 51-multiframes, i.e. 1.882 seconds */
+void bts_cbch_timer_schedule(struct gsm_bts *bts)
+{
+ osmo_timer_setup(&bts->cbch_timer, &bts_cbch_timer, bts);
+ osmo_timer_schedule(&bts->cbch_timer, 1, 882920);
+}
+
+/*! Receive a (decoded) incoming CBCH LOAD IND from given bts. See TS 48.058 8.5.9
+ * \param[in] bts The BTS for which the load indication was received
+ * \param[in] cbch_extended Is this report for extended (true) or basic CBCH
+ * \param[in] is_overflow Is this report and overflow (true) or underflow report
+ * \param[in] slot_count amount of SMSCB messages needed / delay needed */
+int bts_smscb_rx_cbch_load_ind(struct gsm_bts *bts, bool cbch_extended, bool is_overflow,
+ uint8_t slot_count)
+{
+ struct bts_smscb_chan_state *cstate = bts_get_smscb_chan(bts, cbch_extended);
+ int i;
+
+ if (!gsm_bts_get_cbch(bts))
+ return -ENODEV;
+
+ if (is_overflow) {
+ /* halt/delay transmission of further CBCH messages */
+ cstate->overflow = slot_count;
+ } else {
+ for (i = 0; i < slot_count; i++)
+ bts_cbch_send_one(cstate);
+ /* re-schedule the timer to count from now on */
+ bts_cbch_timer_schedule(bts);
+ }
+
+ return 0;
+}