aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/cbch_scheduler.c
blob: ef93b7a4a4a4a1ed777e74416abbd9654d46fbdb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
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;
}