aboutsummaryrefslogtreecommitdiffstats
path: root/src/rlcmac/gre.c
blob: 0eb6a8cada63a8fbe8870d0c35a4effe90ffbdb6 (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
288
289
290
291
292
293
294
/* GPRS RLC/MAC Entity (one per MS) */
/*
 * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 *
 * 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 <stdbool.h>

#include <osmocom/core/bitvec.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>

#include <osmocom/gprs/rlcmac/rlcmac.h>
#include <osmocom/gprs/rlcmac/rlcmac_prim.h>
#include <osmocom/gprs/rlcmac/rlcmac_private.h>
#include <osmocom/gprs/rlcmac/tbf_dl.h>
#include <osmocom/gprs/rlcmac/tbf_dl_ass_fsm.h>
#include <osmocom/gprs/rlcmac/tbf_ul_fsm.h>
#include <osmocom/gprs/rlcmac/tbf_ul.h>
#include <osmocom/gprs/rlcmac/gre.h>
#include <osmocom/gprs/rlcmac/rlcmac_enc.h>


/* We have to defer going to CCCH a bit to leave space for last PKT CTRL ACK to be transmitted */
static void _defer_pkt_idle_timer_cb(void *data)
{
	gprs_rlcmac_submit_l1ctl_pdch_rel_req();
	/* Wait for L1CTL-CCCH_READY.ind before attempting new pkt-access-procedure if needed. */
}

struct gprs_rlcmac_entity *gprs_rlcmac_entity_alloc(uint32_t tlli)
{
	struct gprs_rlcmac_entity *gre;
	int rc;

	gre = talloc_zero(g_rlcmac_ctx, struct gprs_rlcmac_entity);
	if (!gre)
		return NULL;

	osmo_timer_setup(&gre->defer_pkt_idle_timer, _defer_pkt_idle_timer_cb, gre);

	gre->llc_queue = gprs_rlcmac_llc_queue_alloc(gre);
	if (!gre->llc_queue)
		goto err_free_gre;
	gprs_rlcmac_llc_queue_set_codel_params(gre->llc_queue,
					       g_rlcmac_ctx->cfg.codel.use,
					       g_rlcmac_ctx->cfg.codel.interval_msec);

	rc = gprs_rlcmac_tbf_dl_ass_fsm_constructor(&gre->dl_tbf_dl_ass_fsm, gre);
	if (rc < 0)
		goto err_free_gre;

	gre->tlli = tlli;
	gre->ptmsi = GSM_RESERVED_TMSI;
	llist_add_tail(&gre->entry, &g_rlcmac_ctx->gre_list);

	return gre;

err_free_gre:
	talloc_free(gre);
	return NULL;
}

void gprs_rlcmac_entity_free(struct gprs_rlcmac_entity *gre)
{
	if (!gre)
		return;

	gre->freeing = true;

	osmo_timer_del(&gre->defer_pkt_idle_timer);

	gprs_rlcmac_tbf_dl_ass_fsm_destructor(&gre->dl_tbf_dl_ass_fsm);
	gprs_rlcmac_dl_tbf_free(gre->dl_tbf);
	gprs_rlcmac_ul_tbf_free(gre->ul_tbf);
	gprs_rlcmac_llc_queue_free(gre->llc_queue);
	llist_del(&gre->entry);
	talloc_free(gre);
}

/* Called by dl_tbf destructor to inform the DL TBF pointer has been freed.
 * Hence memory pointed by "dl_tbf" is already freed and shall not be accessed. */
void gprs_rlcmac_entity_dl_tbf_freed(struct gprs_rlcmac_entity *gre, const struct gprs_rlcmac_dl_tbf *dl_tbf)
{
	OSMO_ASSERT(gre);
	OSMO_ASSERT(gre->dl_tbf);
	OSMO_ASSERT(dl_tbf);

	/* GRE is freeing (destructor being called) do nothing */
	if (gre->freeing)
		return;

	if (gre->dl_tbf != dl_tbf) {
		/* This may happen if we already have a new DL TBF allocated
		 * immediately prior to freeing the old one (PACCH assignment
		 * reusing resources of old one). Nothing to do, simply wait for
		 * new DL TBF to do its job.
		 */
		return;
	}

	gre->dl_tbf = NULL;

	/* Nothing to do, we are still in packet-transfer-mode using UL TBF. */
	if (gre->ul_tbf)
		return;

	/* we have no DL nor UL TBFs. Go back to PACKET-IDLE state, and start
	 * packet-access-procedure if we still have data to be transmitted.
	 */
	/* We have to defer going to CCCH a bit to leave space for last PKT CTRL ACK to be transmitted */
	osmo_timer_schedule(&gre->defer_pkt_idle_timer, 0, DEFER_SCHED_PDCH_REL_REQ_uS);
}

/* Called by ul_tbf destructor to inform the UL TBF pointer has been freed.
 * Hence memory pointed by "ul_tbf" is already freed and shall not be accessed. */
void gprs_rlcmac_entity_ul_tbf_freed(struct gprs_rlcmac_entity *gre, const struct gprs_rlcmac_ul_tbf *ul_tbf)
{
	OSMO_ASSERT(gre);
	OSMO_ASSERT(gre->ul_tbf);
	OSMO_ASSERT(ul_tbf);

	/* GRE is freeing (destructor being called) do nothing */
	if (gre->freeing)
		return;

	if (gre->ul_tbf != ul_tbf) {
		/* This may happen if we already have a new UL TBF allocated
		 * immediately prior to freeing the old one (PACCH assignment
		 * reusing resources of old one). Nothing to do, simply wait for
		 * new UL TBF to do its job.
		 */
		return;
	}

	gre->ul_tbf = NULL;

	/* Nothing to do, dl_tbf will eventually trigger request for UL TBF PACCH assignment. */
	if (gre->dl_tbf)
		return;

	/* we have no DL nor UL TBFs. Go back to PACKET-IDLE state, and start
	 * packet-access-procedure if we still have data to be transmitted.
	 */
	/* We have to defer going to CCCH a bit to leave space for last PKT CTRL ACK to be transmitted */
	osmo_timer_schedule(&gre->defer_pkt_idle_timer, 0, DEFER_SCHED_PDCH_REL_REQ_uS);
}

/* TS 44.060 5.3 In packet idle mode:
* - no temporary block flow (TBF) exists..
* - the mobile station monitors the relevant paging subchannels on CCCH. In packet
* idle mode, upper layers may require the transfer of a upper layer PDU, which
* implicitly triggers the establishment of a TBF and the transition to packet
* transfer mode. In packet idle mode, upper layers may require the establishment
* of an RR connection. When the mobile station enters dedicated mode (see 3GPP TS
* 44.018), it may leave the packet idle mode, if the mobile station limitations
* make it unable to handle the RR connection and the procedures in packet idle
* mode simultaneously.*/
bool gprs_rlcmac_entity_in_packet_idle_mode(const struct gprs_rlcmac_entity *gre)
{
	return !gre->ul_tbf && !gre->dl_tbf;
}

/* TS 44.060 5.4 "In packet transfer mode, the mobile station is allocated radio
* resources providing one or more TBFs. [...]
* When a transfer of upper layer PDUs
* terminates, in either downlink or uplink direction, the corresponding TBF is
* released. In packet transfer mode, when all TBFs have been released, in downlink
* and uplink direction, the mobile station returns to packet idle mode."
*/
bool gprs_rlcmac_entity_in_packet_transfer_mode(const struct gprs_rlcmac_entity *gre)
{
	return gre->ul_tbf || gre->dl_tbf;
}

/* Whether MS has data queued from upper layers waiting to be transmitted in the
 * Tx queue (an active UL TBF may still have some extra data) */
bool gprs_rlcmac_entity_have_tx_data_queued(const struct gprs_rlcmac_entity *gre)
{
	return gprs_rlcmac_llc_queue_size(gre->llc_queue) > 0;
}

/* Create a new UL TBF and start Packet access procedure to get an UL assignment if needed */
int gprs_rlcmac_entity_start_ul_tbf_pkt_acc_proc_if_needed(struct gprs_rlcmac_entity *gre)
{
	enum osmo_gprs_rlcmac_llc_sapi tx_sapi;
	enum gprs_rlcmac_tbf_ul_ass_type ul_ass_type;

	/* TS 44.060 5.3 "In packet idle mode, upper layers may require the
	* transfer of a upper layer PDU, which implicitly triggers the
	* establishment of a TBF and the transition to packet transfer mode." */
	if (!gprs_rlcmac_entity_in_packet_idle_mode(gre))
		return 0;

	if (!gprs_rlcmac_entity_have_tx_data_queued(gre))
		return 0;

	OSMO_ASSERT(!gre->ul_tbf);
	/* We have data in the queue but we have no ul_tbf. Allocate one and start UL Assignment. */
	gre->ul_tbf = gprs_rlcmac_ul_tbf_alloc(gre);
	if (!gre->ul_tbf)
		return -ENOMEM;

	tx_sapi = gprs_rlcmac_llc_queue_highest_radio_prio_pending(gre->llc_queue);
	/* 3GPP TS 44.018 3.5.2.1.2 "Initiation of the packet access procedure: channel request" */
	switch (tx_sapi) {
	case OSMO_GPRS_RLCMAC_LLC_SAPI_GMM:
		/* "If the purpose [...] is to send a Page Response, a Cell update (the mobile
		 * station was in GMM READY state before the cell reselection) or for any other
		 * GPRS Mobility Management or GPRS Session Management procedure, the mobile station
		 * shall request a one phase packet access" */
		ul_ass_type = GPRS_RLCMAC_TBF_UL_ASS_TYPE_1PHASE;
		break;
	default:
		/* "If the purpose [...] is to send user data and the requested RLC mode is
		 * acknowledged mode, the mobile station shall request either a one phase packet
		 * access or a single block packet access." */
		/* TODO: We always use 1phase for now... ideally we should decide
		 * based on amount of Tx data and configured MultiSlot Class? */
		ul_ass_type = GPRS_RLCMAC_TBF_UL_ASS_TYPE_1PHASE;
	}
	/* We always use 1phase for now... */
	return gprs_rlcmac_tbf_ul_ass_start(gre->ul_tbf, ul_ass_type);
}

int gprs_rlcmac_entity_llc_enqueue(struct gprs_rlcmac_entity *gre,
				   const uint8_t *ll_pdu, unsigned int ll_pdu_len,
				   enum osmo_gprs_rlcmac_llc_sapi sapi,
				   enum gprs_rlcmac_radio_priority radio_prio)
{
	int rc;
	LOGGRE(gre, LOGL_DEBUG, "Enqueueing LLC-PDU len=%u SAPI=%s radio_prio=%u\n",
	       ll_pdu_len, get_value_string(osmo_gprs_rlcmac_llc_sapi_names, sapi), radio_prio + 1);
	rc = gprs_rlcmac_llc_queue_enqueue(gre->llc_queue, ll_pdu, ll_pdu_len,
					   sapi, radio_prio);
	if (rc < 0) {
		LOGGRE(gre, LOGL_NOTICE, "Enqueueing LLC-PDU len=%u SAPI=%s radio_prio=%u failed!\n",
		       ll_pdu_len, get_value_string(osmo_gprs_rlcmac_llc_sapi_names, sapi), radio_prio + 1);
		return rc;
	}

	rc = gprs_rlcmac_entity_start_ul_tbf_pkt_acc_proc_if_needed(gre);
	return rc;
}

struct msgb *gprs_rlcmac_gre_create_pkt_ctrl_ack(const struct gprs_rlcmac_entity *gre)
{
	struct msgb *msg;
	struct bitvec bv;
	RlcMacUplink_t ul_block;
	int rc;

	OSMO_ASSERT(gre);

	msg = msgb_alloc(GSM_MACBLOCK_LEN, "pkt_ctrl_ack");
	if (!msg)
		return NULL;

	/* Initialize a bit vector that uses allocated msgb as the data buffer. */
	bv = (struct bitvec){
		.data = msgb_put(msg, GSM_MACBLOCK_LEN),
		.data_len = GSM_MACBLOCK_LEN,
	};
	bitvec_unhex(&bv, GPRS_RLCMAC_DUMMY_VEC);

	gprs_rlcmac_enc_prepare_pkt_ctrl_ack(&ul_block, gre->tlli);
	rc = osmo_gprs_rlcmac_encode_uplink(&bv, &ul_block);
	if (rc < 0) {
		LOGGRE(gre, LOGL_ERROR, "Encoding of Packet Control ACK failed (%d)\n", rc);
		goto free_ret;
	}
	LOGGRE(gre, LOGL_DEBUG, "Tx Packet Control Ack\n");

	return msg;

free_ret:
	msgb_free(msg);
	return NULL;
}