summaryrefslogtreecommitdiffstats
path: root/openbsc/src/libbsc/pdch_defrag.c
blob: 5f075f65d35c4142ee143e0988e8573563d7dac7 (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
/* (C) 2013 by Andreas Eversberg <andreas@eversberg.eu>
 *
 * 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 <stdlib.h>
#include <errno.h>

#include <openbsc/debug.h>
#include <openbsc/gsm_data.h>
#include <openbsc/handover.h>
#include <openbsc/pdch_defrag.h>
#include <openbsc/chan_alloc.h>

static struct gsm_lchan *find_shared_forward(struct gsm_bts *bts)
{
	struct gsm_bts_trx *trx;
	struct gsm_lchan *lc;
	int j;

	llist_for_each_entry(trx, &bts->trx_list, list) {
		for (j = 0; j < 8; j++) {
			/* find first TCH/F+PDCH */
			if (trx->ts[j].pchan == GSM_PCHAN_TCH_F_PDCH) {
				lc = &trx->ts[j].lchan[0];
				if (lc->state == LCHAN_S_ACTIVE)
					return lc;
			}
		}
	}

	return NULL;
}

static struct gsm_lchan *find_shared_reverse(struct gsm_bts *bts)
{
	struct gsm_bts_trx *trx;
	struct gsm_lchan *lc;
	int j;

	llist_for_each_entry_reverse(trx, &bts->trx_list, list) {
		for (j = 7; j >= 0; j--) {
			/* find last TCH/F+PDCH */
			if (trx->ts[j].pchan == GSM_PCHAN_TCH_F_PDCH) {
				lc = &trx->ts[j].lchan[0];
				if (lc->state == LCHAN_S_ACTIVE)
					return lc;
			}
		}
	}

	return NULL;
}

static void debug_bts_trx_ts(struct gsm_bts_trx_ts *ts)
{
	LOGPC(DHODEC, LOGL_DEBUG, " (BTS %u, TRX %u, TS %u)\n",
		ts->trx->bts->nr, ts->trx->nr, ts->nr);
}

/* Try to move a TCH/F+PDCH candidate to maintain as much free and consecutive
 * GSM_PCHAN_TCH_F_PDCH slots as possible.
 *
 * This algorithm is triggered whenever a channel has been released.
 *
 * The last (highest TRX/TS number) and active TCH/F+PDCH is searched. If there
 * is a TCH/F or a free TCH/F+PDCH on a lower TRX/TS number, assignment is
 * triggered. (When allocation order is reversed, this algorithm handles TRX/TS
 * numbers in reversed order also.
 *
 * This algorithm ensures that gaps between active TCH/F+PDCH channels are
 * removed, by assigning active connections to standard TCH/F or by assigning
 * them to consecutive TCH/F+PDCH channels.
 */
void do_pdch_defrag(struct gsm_bts *bts)
{
	struct gsm_lchan *old_lchan = NULL, *new_lchan;
	int rc;

	/* search TCH/F+PDCH in reverse order of allocation */
	if (bts->chan_alloc_reverse) {
		/* search from first to last TRX/TS */
		old_lchan = find_shared_forward(bts);
	} else {
		/* search from last to first TRX/TS */
		old_lchan = find_shared_reverse(bts);
	}

	/* if there is no candidate on GSM_PCHAN_TCH_F_PDCH, we are done */
	if (!old_lchan)
		return;

	LOGP(DHODEC, LOGL_DEBUG, "A shared TCH/F+PDCH is in use, check if we "
		"can move connection.");
	debug_bts_trx_ts(old_lchan->ts);

	/* allocate new lchan */
	new_lchan = lchan_alloc(bts, GSM_LCHAN_TCH_F, 0);
	/* no free destination for candidate */
	if (!new_lchan) {
		LOGP(DHODEC, LOGL_DEBUG, "-> Cannot move, no other TCH/F "
			"available.\n");
		return;
	}

	/* if new TS is also a GSM_PCHAN_TCH_F_PDCH */
	if (new_lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH) {
		LOGP(DHODEC, LOGL_DEBUG, "-> New channel for candidate is also "
			"a TCH/F+PDCH.");
		debug_bts_trx_ts(new_lchan->ts);
		/* be sure to move down (or up at reverse allocation) */
		if (bts->chan_alloc_reverse) {
			/* new TS is lower */
			if (new_lchan->ts->trx->nr < old_lchan->ts->trx->nr) {
				LOGP(DHODEC, LOGL_DEBUG, "-> Move does not "
					"lower fragmentation, new TRX has "
					"lower number.\n");
				goto free_lchan;
			}
			if (new_lchan->ts->trx->nr == old_lchan->ts->trx->nr
			 && new_lchan->ts->nr < old_lchan->ts->nr) {
				LOGP(DHODEC, LOGL_DEBUG, "-> Move does not "
					"lower fragmentation, new TS has "
					"lower number.\n");
				goto free_lchan;
			}
		} else {
			/* new TS is higher */
			if (new_lchan->ts->trx->nr > old_lchan->ts->trx->nr) {
				goto free_lchan;
				LOGP(DHODEC, LOGL_DEBUG, "-> Move does not "
					"lower fragmentation, new TRX has "
					"higher number.\n");
			}
			if (new_lchan->ts->trx->nr == old_lchan->ts->trx->nr
			 && new_lchan->ts->nr > old_lchan->ts->nr) {
				LOGP(DHODEC, LOGL_DEBUG, "-> Move does not "
					"lower fragmentation, new TS has "
					"higher number.\n");
				goto free_lchan;
			}
		}
	} else {
		LOGP(DHODEC, LOGL_DEBUG, "-> New channel for candidate is a "
			"TCH/F.");
		debug_bts_trx_ts(new_lchan->ts);
	}

	rc = bsc_handover_start(old_lchan, new_lchan, new_lchan->ts->trx->bts,
		1);
	if (rc) {
		LOGP(DHODEC, LOGL_NOTICE, "Cannot trigger assignment to "
			"defragment TCH/F+PDCH slots.\n");
	} else {
		LOGP(DHODEC, LOGL_NOTICE, "Triggered assignment to defragment "
			"TCH/F+PDCH slots.\n");
	}

	/* We do not need to check, if assignment is ongoing or if it fails,
	 * because in both cases a channel is freed, so this defragmentation
	 * is triggered again.
	 */

	return;

free_lchan:
	lchan_free(new_lchan);
}