/* CBCH (Cell Broadcast Channel) Scheduler for OsmoBSC */ /* * (C) 2019 by Harald Welte * * 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 . * */ #include #include #include #include #include #include #include #include #include /* 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); if (last_idx >= sched_arr_size) return -ERANGE; 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 + smscb->input.rep_period < 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; }