blob: aa659a3e615494a31f303c26a4685cb05b73d24a [file] [log] [blame]
Philipp Maier4bac3982023-02-03 16:52:42 +01001/*
2 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
3 * All Rights Reserved
4 *
5 * Author: Philipp Maier
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22#include <er_ccu_if.h>
23#include <er_ccu_descr.h>
24#include <string.h>
25#include <errno.h>
26
27#include <osmocom/abis/e1_input.h>
28#include <osmocom/abis/abis.h>
29#include <osmocom/trau/trau_sync.h>
30#include <osmocom/trau/trau_pcu_ericsson.h>
31#include <bts.h>
32#include <gprs_debug.h>
33#include <pcu_l1_if.h>
34
35#define E1_TS_BYTES 160
36#define DEBUG_BITS_MAX 1280
37#define DEBUG_BYTES_MAX 40
38
39#define LOGPCCU(ccu_descr, level, tag, fmt, args...) \
40 LOGP(DE1, level, "E1TS(%u:%u:%u) %s:" fmt, \
41 ccu_descr->e1_conn_pars->e1_nr, ccu_descr->e1_conn_pars->e1_ts, \
42 ccu_descr->e1_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL ? 0 : ccu_descr->e1_conn_pars->e1_ts_ss, tag, \
43 ## args)
44
45struct e1_ts_descr {
46 uint8_t usecount;
47 bool i460_ts_initialized;
48 struct osmo_i460_timeslot i460_ts;
49};
50
51struct e1_line_descr {
52 struct e1_ts_descr e1_ts[NUM_E1_TS - 1];
53};
54
55static struct e1_line_descr e1_lines[32];
56static void *tall_ccu_ctx = NULL;
57
58static const struct e1inp_line_ops dummy_e1_line_ops = {
59 .sign_link_up = NULL,
60 .sign_link_down = NULL,
61 .sign_link = NULL,
62};
63
64/* called by trau frame synchronizer: feed received MAC blocks into PCU */
65static void sync_frame_out_cb(void *user_data, const ubit_t *bits, unsigned int num_bits)
66{
67 struct er_ccu_descr *ccu_descr = user_data;
68
69 if (!bits || num_bits == 0)
70 return;
71
72 LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-RX", "receiving %u TRAU frame bits from subslot (synchronized): %s...\n",
73 num_bits, osmo_ubit_dump(bits, num_bits > DEBUG_BITS_MAX ? DEBUG_BITS_MAX : num_bits));
74
75 ccu_descr->er_ccu_rx_cb(ccu_descr, bits, num_bits);
76}
77
78/* called by I.460 de-multiplexer: feed output of I.460 demux into TRAU frame sync */
79static void e1_i460_demux_bits_cb(struct osmo_i460_subchan *schan, void *user_data, const ubit_t *bits,
80 unsigned int num_bits)
81{
82 struct er_ccu_descr *ccu_descr = user_data;
83
84 LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-RX", "receiving %u TRAU frame bits from subslot: %s...\n", num_bits,
85 osmo_ubit_dump(bits, num_bits > DEBUG_BITS_MAX ? DEBUG_BITS_MAX : num_bits));
86
87 OSMO_ASSERT(ccu_descr->link.trau_sync_fi);
88 osmo_trau_sync_rx_ubits(ccu_descr->link.trau_sync_fi, bits, num_bits);
89
90}
91
92/* called by I.460 de-multiplexer: ensure that sync indications are sent when mux buffer runs empty */
93static void e1_i460_mux_empty_cb(struct osmo_i460_subchan *schan2, void *user_data)
94{
95 struct er_ccu_descr *ccu_descr = user_data;
96
97 LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-TX", "demux buffer empty\n");
98 ccu_descr->er_ccu_empty_cb(ccu_descr);
99}
100
101/* handle outgoing E1 traffic */
102static void e1_send_ts_frame(struct e1inp_ts *ts)
103{
104 void *ctx = tall_ccu_ctx;
105 struct e1_ts_descr *ts_descr;
106 struct msgb *msg;
107 uint8_t *ptr;
108
109 /* The line number and ts number that arrives here should be clean. */
110 OSMO_ASSERT(ts->line->num < ARRAY_SIZE(e1_lines));
111
112 ts_descr = &e1_lines[ts->line->num].e1_ts[ts->num];
113
114 /* Do not send anything in case the E1 timeslot is not ready. */
115 if (ts_descr->usecount == 0)
116 return;
117
118 /* Get E1 frame from I.460 multiplexer */
119 msg = msgb_alloc_c(ctx, E1_TS_BYTES, "E1-TX-timeslot-bytes");
120 ptr = msgb_put(msg, E1_TS_BYTES);
121 osmo_i460_mux_out(&ts_descr->i460_ts, ptr, E1_TS_BYTES);
122
123 LOGPITS(ts, DE1, LOGL_DEBUG, "E1-TX: sending %u bytes: %s...\n",
124 msgb_length(msg), osmo_hexdump_nospc(msgb_data(msg),
125 msgb_length(msg) >
126 DEBUG_BYTES_MAX ? DEBUG_BYTES_MAX : msgb_length(msg)));
127
128 /* Hand data over to the E1 stack */
129 msgb_enqueue(&ts->raw.tx_queue, msg);
130}
131
132/* Callback function to handle incoming E1 traffic */
133static void e1_recv_cb(struct e1inp_ts *ts, struct msgb *msg)
134{
135 struct e1_ts_descr *ts_descr;
136
137 if (msg->len != E1_TS_BYTES) {
138 LOGPITS(ts, DE1, LOGL_ERROR,
139 "E1-RX: receiving bad, expected length is %u, actual length is %u!\n",
140 E1_TS_BYTES, msg->len);
141 msgb_free(msg);
142 return;
143 }
144
145 LOGPITS(ts, DE1, LOGL_DEBUG, "E1-RX: receiving %u bytes: %s ...\n",
146 msg->len, osmo_hexdump_nospc(msg->data, msg->len));
147
148 /* Note: The line number and ts number that arrives here should be clean. */
149 OSMO_ASSERT(ts->line->num < ARRAY_SIZE(e1_lines));
150 ts_descr = &e1_lines[ts->line->num].e1_ts[ts->num];
151
152 /* Hand data over to the I640 demultiplexer. */
153 osmo_i460_demux_in(&ts_descr->i460_ts, msg->data, msg->len);
154
155 /* Trigger sending of pending E1 traffic */
156 e1_send_ts_frame(ts);
157
158 /* e1inp_rx_ts(), the caller of this callback does not free() msgb. */
159 msgb_free(msg);
160}
161
162static struct e1_ts_descr *ts_descr_from_ccu_descr(struct er_ccu_descr *ccu_descr)
163{
164 /* Make sure E1 line number is valid */
165 if (ccu_descr->e1_conn_pars->e1_nr >= ARRAY_SIZE(e1_lines)) {
166 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "Invalid E1 line number!\n");
167 return NULL;
168 }
169
170 /* Make sure E1 timeslot number is valid */
171 if (ccu_descr->e1_conn_pars->e1_ts < 1 || ccu_descr->e1_conn_pars->e1_ts > NUM_E1_TS - 1) {
172 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "Invalid E1 timeslot number!\n");
173 return NULL;
174 }
175
176 /* Timeslots are only initialized once and will stay open after that. */
177 return &e1_lines[ccu_descr->e1_conn_pars->e1_nr].e1_ts[ccu_descr->e1_conn_pars->e1_ts];
178}
179
180/* Configure an I.460 subslot and add it to the CCU descriptor */
181static int add_i460_subslot(void *ctx, struct er_ccu_descr *ccu_descr)
182{
183 struct e1_ts_descr *ts_descr;
184 enum osmo_tray_sync_pat_id sync_pattern;
185
186 if (ccu_descr->link.schan) {
187 /* NOTE: This is a serious error: subslots should be removed when l1if_close_pdch() is called by the
188 * PCU. This log line points towards a problem with the PDCH management inside the PCU! */
189 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "I.460 subslot is already configured -- will not touch it!\n");
190 return -EINVAL;
191 }
192
193 ts_descr = ts_descr_from_ccu_descr(ccu_descr);
194 if (!ts_descr)
195 return -EINVAL;
196 if (ts_descr->usecount == 0) {
197 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "E1 timeslot not ready!\n");
198 return -EINVAL;
199 }
200
201 /* Set up I.460 subchannel and connect it to the MUX on the E1 timeslot */
202 if (ccu_descr->e1_conn_pars->e1_ts_ss == E1_SUBSLOT_FULL) {
203 LOGPCCU(ccu_descr, LOGL_INFO, "SETUP", "using 64k subslots\n");
204 ccu_descr->link.scd.rate = OSMO_I460_RATE_64k;
205 ccu_descr->link.scd.demux.num_bits = E1_TS_BYTES * 8;
206 ccu_descr->link.scd.bit_offset = 0;
207 sync_pattern = OSMO_TRAU_SYNCP_64_ER_CCU;
208 } else {
209 LOGPCCU(ccu_descr, LOGL_INFO, "SETUP", "using 16k subslots\n");
210 if (ccu_descr->e1_conn_pars->e1_ts_ss > 3) {
211 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "Invalid I.460 subslot number!\n");
212 return -EINVAL;
213 }
214 ccu_descr->link.scd.rate = OSMO_I460_RATE_16k;
215 ccu_descr->link.scd.demux.num_bits = E1_TS_BYTES / 4 * 8;
216 ccu_descr->link.scd.bit_offset = ccu_descr->e1_conn_pars->e1_ts_ss * 2;
217 sync_pattern = OSMO_TRAU_SYNCP_16_ER_CCU;
218 }
219
220 ccu_descr->link.scd.demux.out_cb_bits = e1_i460_demux_bits_cb;
221 ccu_descr->link.scd.demux.out_cb_bytes = NULL;
222 ccu_descr->link.scd.demux.user_data = ccu_descr;
223 ccu_descr->link.scd.mux.in_cb_queue_empty = e1_i460_mux_empty_cb;
224 ccu_descr->link.scd.mux.user_data = ccu_descr;
225
226 LOGPCCU(ccu_descr, LOGL_INFO, "SETUP", "adding I.460 subchannel: bit_offset=%u, num_bits=%zu\n",
227 ccu_descr->link.scd.bit_offset, ccu_descr->link.scd.demux.num_bits);
228 ccu_descr->link.schan = osmo_i460_subchan_add(ctx, &ts_descr->i460_ts, &ccu_descr->link.scd);
229 if (!ccu_descr->link.schan) {
230 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "adding I.460 subchannel: failed!\n");
231 return -EINVAL;
232 }
233
234 /* Configure TRAU synchronizer */
235 ccu_descr->link.trau_sync_fi = osmo_trau_sync_alloc(tall_ccu_ctx, "trau-sync", sync_frame_out_cb, sync_pattern, ccu_descr);
236 if (!ccu_descr->link.trau_sync_fi) {
237 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "adding I.460 TRAU frame sync: failed!\n");
238 return -EINVAL;
239 }
240
241 /* Ericsson uses a different synchronization pattern for MCS9 TRAU frames */
242 if (sync_pattern == OSMO_TRAU_SYNCP_64_ER_CCU)
243 osmo_trau_sync_set_secondary_pat(ccu_descr->link.trau_sync_fi, OSMO_TRAU_SYNCP_64_ER_CCU_MCS9, 1);
244
245 return 0;
246}
247
248/* Remove an I.460 subslot from the CCU descriptor */
249static void del_i460_subslot(struct er_ccu_descr *ccu_descr)
250{
251 if (ccu_descr->link.schan)
252 osmo_i460_subchan_del(ccu_descr->link.schan);
253 ccu_descr->link.schan = NULL;
254 if (ccu_descr->link.trau_sync_fi)
255 osmo_fsm_inst_term(ccu_descr->link.trau_sync_fi, OSMO_FSM_TERM_REGULAR, NULL);
256 ccu_descr->link.trau_sync_fi = NULL;
257
258 memset(&ccu_descr->link.scd, 0, sizeof(ccu_descr->link.scd));
259}
260
261/* Configure an E1 timeslot according to the description in the ccu_descr */
262static int open_e1_timeslot(struct er_ccu_descr *ccu_descr)
263{
264 struct e1inp_line *e1_line;
265 struct e1_ts_descr *ts_descr;
266 int rc;
267
268 /* Find timeslot descriptor and check if the timeslot is already open. */
269 ts_descr = ts_descr_from_ccu_descr(ccu_descr);
270 if (!ts_descr)
271 return -EINVAL;
272 if (ts_descr->usecount > 0) {
273 LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP", "E1 timeslot already open -- using it as it is!\n");
274 ts_descr->usecount++;
275 return 0;
276 }
277
278 /* Find and set up E1 line */
279 e1_line = e1inp_line_find(ccu_descr->e1_conn_pars->e1_nr);
280 if (!e1_line) {
281 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "no such E1 line!\n");
282 return -EINVAL;
283 }
284 e1inp_line_bind_ops(e1_line, &dummy_e1_line_ops);
285
286 /* Set up E1 timeslot */
287 rc = e1inp_ts_config_raw(&e1_line->ts[ccu_descr->e1_conn_pars->e1_ts - 1], e1_line, e1_recv_cb);
288 if (rc < 0) {
289 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "configuration of timeslot failed!\n");
290 return -EINVAL;
291 }
292 rc = e1inp_line_update(e1_line);
293 if (rc < 0) {
294 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "line update failed!\n");
295 return -EINVAL;
296 }
297
298 /* Make sure the i460 mux is ready */
299 if (!ts_descr->i460_ts_initialized) {
300 osmo_i460_ts_init(&ts_descr->i460_ts);
301 ts_descr->i460_ts_initialized = true;
302 }
303
304 ts_descr->usecount++;
305 OSMO_ASSERT(ts_descr->usecount == 1);
306
307 return 0;
308}
309
310/* Configure an E1 timeslot according to the description in the ccu_descr */
311static int close_e1_timeslot(struct er_ccu_descr *ccu_descr)
312{
313 struct e1inp_line *e1_line;
314 struct e1_ts_descr *ts_descr;
315 int rc;
316
317 /* Find timeslot descriptor and check if the timeslot is still used by another subslot. */
318 ts_descr = ts_descr_from_ccu_descr(ccu_descr);
319 if (!ts_descr)
320 return -EINVAL;
321 if (ts_descr->usecount > 1) {
322 LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP",
323 "E1 timeslot still in used by another subslot, leaving it open!\n");
324 ts_descr->usecount--;
325 return 0;
326 } else if (ts_descr->usecount == 0) {
327 /* This should not be as it means we close the timeslot too often. */
328 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "E1 timeslot already closed, leaving it as it is...\n");
329 return -EINVAL;
330 }
331
332 /* Find E1 line */
333 e1_line = e1inp_line_find(ccu_descr->e1_conn_pars->e1_nr);
334 if (!e1_line) {
335 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "no such E1 line!\n");
336 return -EINVAL;
337 }
338
339 /* Release E1 timeslot */
340 rc = e1inp_ts_config_none(&e1_line->ts[ccu_descr->e1_conn_pars->e1_ts - 1], e1_line);
341 if (rc < 0) {
342 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "failed to disable E1 timeslot!\n");
343 return -EINVAL;
344 }
345 rc = e1inp_line_update(e1_line);
346 if (rc < 0) {
347 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "failed to update E1 line!\n");
348 return -EINVAL;
349 }
350
351 ts_descr->usecount--;
352 OSMO_ASSERT(ts_descr->usecount == 0);
353
354 return 0;
355}
356
357int er_ccu_if_open(struct er_ccu_descr *ccu_descr)
358{
359 if (ccu_descr->link.ccu_connected) {
360 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP",
361 "cannot connect CCU since it is already connected -- ignored!\n");
362 return 0;
363 }
364
365 if (open_e1_timeslot(ccu_descr) < 0)
366 return -EINVAL;
367
368 if (add_i460_subslot(tall_ccu_ctx, ccu_descr) < 0)
369 return -EINVAL;
370
371 ccu_descr->link.ccu_connected = true;
372 LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP", "CCU connected.\n");
373 return 0;
374}
375
376void er_ccu_if_close(struct er_ccu_descr *ccu_descr)
377{
378 if (!ccu_descr->link.ccu_connected) {
379 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP",
380 "cannot disconnect CCU since it is already disconnected -- ignored!\n");
381 return;
382 }
383
384 del_i460_subslot(ccu_descr);
385 close_e1_timeslot(ccu_descr);
386
387 ccu_descr->link.ccu_connected = false;
388 LOGPCCU(ccu_descr, LOGL_DEBUG, "SETUP", "CCU disconnected.\n");
389}
390
391void er_ccu_if_tx(struct er_ccu_descr *ccu_descr, const ubit_t *bits, unsigned int num_bits)
392{
393 struct msgb *msg;
394 uint8_t *ptr;
395
396 if (!ccu_descr->link.ccu_connected) {
397 LOGPCCU(ccu_descr, LOGL_ERROR, "SETUP", "cannot TX block, CCU is disconnected -- ignored!\n");
398 return;
399 }
400
401 msg = msgb_alloc_c(tall_ccu_ctx, num_bits, "E1-I.460-PCU-IND-frame");
402 ptr = msgb_put(msg, num_bits);
403 memcpy(ptr, bits, num_bits);
404 LOGPCCU(ccu_descr, LOGL_DEBUG, "I.460-TX", "sending %u bits: %s...\n", msgb_length(msg),
405 osmo_ubit_dump(msgb_data(msg), msgb_length(msg) > DEBUG_BITS_MAX ? DEBUG_BITS_MAX : msgb_length(msg)));
406 osmo_i460_mux_enqueue(ccu_descr->link.schan, msg);
407}
408
409void er_ccu_if_init(void *ctx)
410{
411 libosmo_abis_init(ctx);
412 e1inp_vty_init();
413
414 tall_ccu_ctx = talloc_new(ctx);
415 memset(e1_lines, 0, sizeof(e1_lines));
416}