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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
|
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TRX Toolkit
# Transceiver implementation
#
# (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
# Contributions by sysmocom - s.f.m.c. GmbH
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import logging as log
import threading
from ctrl_if_trx import CTRLInterfaceTRX
from data_if import DATAInterface
from udp_link import UDPLink
from trx_list import TRXList
from gsm_shared import HoppingParams
class Transceiver:
""" Base transceiver implementation.
Represents a single transceiver, that can be used as for the BTS side,
as for the MS side. Each individual instance of Transceiver unifies
three basic interfaces built on three independent UDP connections:
- CLCK (base port + 100/0) - clock indications from TRX to L1,
- CTRL (base port + 101/1) - control interface for L1,
- DATA (base port + 102/2) - bidirectional data interface for bursts.
A transceiver can be either in active (i.e. working), or in idle mode.
The active mode should ensure that both RX/TX frequencies are set.
NOTE: CLCK is not required for some L1 implementations, so it is optional.
== Timeslot configuration
Transceiver has a list of active (i.e. configured) TDMA timeslots.
The L1 should configure a timeslot before sending or expecting any
data on it. This is done by SETSLOT control command, which also
indicates an associated channel combination (see GSM TS 05.02).
NOTE: we don't store the associated channel combinations,
as they are only useful for burst detection and demodulation.
== Child transceivers
A BTS can (optionally) have more than one transceiver. In this case
additional (let's say child) transceivers basically share the same
clock source of the first transceiver, so UDP port mapping is a bit
different, for example:
(trx_0) clck=5700, ctrl=5701, data=5702,
(trx_1) ctrl=5703, data=5704,
(trx_2) ctrl=5705, data=5706.
...
As soon as the first transceiver is powered on / off,
all child transceivers are also powered on / off.
== Clock distribution (optional)
The clock indications are not expected by L1 when transceiver
is not running, so we monitor both POWERON / POWEROFF events
from the control interface, and keep the list of CLCK links
in a given CLCKGen instance updated. The clock generator is
started and stopped automatically.
NOTE: a single instance of CLCKGen can be shared between multiple
transceivers, as well as multiple transceivers may use
individual CLCKGen instances.
== Power Measurement (optional)
Transceiver may have an optional power measurement interface,
that shall provide at least one method: measure(freq). This
is required for the MS side (i.e. OsmocomBB).
== Frequency hopping (optional)
There are two ways to implement frequency hopping:
a) The Transceiver is configured with the hopping parameters, in
particular HSN, MAIO, and the list of ARFCNs (channels), so the
actual Rx/Tx frequencies are changed by the Transceiver itself
depending on the current TDMA frame number.
b) The L1 maintains several Transceivers (two or more), so each
instance is assigned one dedicated RF carrier frequency, and
hence the number of available hopping frequencies is equal to
the number of Transceivers. In this case, it's the task of
the L1 to commutate bursts between Transceivers (frequencies).
Variant a) is commonly known as "synthesizer frequency hopping"
whereas b) is known as "baseband frequency hopping".
For the MS side, a) is preferred, because a phone usually has only
one Transceiver (per RAT). On the other hand, b) is more suitable
for the BTS side, because it's relatively easy to implement and
there is no technical limitation on the amount of Transceivers.
FakeTRX obviously does support b) since multi-TRX feature has been
implemented, as well as a) by resolving UL/DL frequencies using a
preconfigured (by the L1) set of the hopping parameters. The later
can be enabled using the SETFH control command.
NOTE: in the current implementation, mode a) applies to the whole
Transceiver and all its timeslots, so using in for the BTS side
does not make any sense (imagine BCCH hopping together with DCCH).
== The transmit burst queue
According to 3GPP 45.002, the time difference between Uplink and
Downlink corresponds to three TDMA timeslot periods. However,
in general the L1 implementations (such as osmo-bts-trx and trxcon)
never schedule to be transmitted bursts for the current TDMA frame
immediately. Instead, they are being scheduled prematurely.
The rationale is that both transceiver and the L1 implementation
are separete processes that are not perfectly synchronized in time.
Moreover, the transceiver needs some time to prepare a burst for
transmission. This is why the time difference between Uplink and
Downlink is actually much higher on practice (20 TDMA frame periods
by default, at the moment of writing this patch).
In order to reflect that delay in a virtual environment, this
implementation, just like a normal transceiver (e.g. osmo-trx),
queues all to be transmitted (L12TRX) bursts, so hey remain in
the transmit queue until the appropriate time of transmission.
The API user is supposed to call recv_data_msg() in order to obtain
a L12TRX message on the TRXD (data) inteface, so it gets queued by
this function. Then, to ensure the timeous transmission, the user
of this implementation needs to call clck_tick() on each TDMA
frame. Both functions are thread-safe (queue mutex).
In a multi-trx configuration, the use of queue additionally ensures
proper burst aggregation on multiple TRXD connections, so all L12TRX
messages are guaranteed to be sent in the right order, i.e. with
monolithically-increasing TDMA frame numbers.
"""
def __init__(self, bind_addr, remote_addr, base_port, name = None,
child_idx = 0, clck_gen = None, pwr_meas = None):
# Connection info
self.remote_addr = remote_addr
self.bind_addr = bind_addr
self.base_port = base_port
self.child_idx = child_idx
# Meta info
self.name = name
log.info("Init transceiver '%s'" % self)
# Child transceiver cannot have its own clock
if clck_gen is not None and child_idx > 0:
raise TypeError("Child transceiver cannot have its own clock")
# Init DATA interface
self.data_if = DATAInterface(
remote_addr, base_port + child_idx * 2 + 102,
bind_addr, base_port + child_idx * 2 + 2)
# Init CTRL interface
self.ctrl_if = CTRLInterfaceTRX(self,
remote_addr, base_port + child_idx * 2 + 101,
bind_addr, base_port + child_idx * 2 + 1)
# Init optional CLCK interface
self.clck_gen = clck_gen
if clck_gen is not None:
self.clck_if = UDPLink(
remote_addr, base_port + 100,
bind_addr, base_port)
# Optional Power Measurement interface
self.pwr_meas = pwr_meas
# Internal state
self.running = False
# Actual RX / TX frequencies
self._rx_freq = None
self._tx_freq = None
# Frequency hopping parameters (set by CTRL)
self.fh = None
# List of active (configured) timeslots
self.ts_list = []
# List of child transceivers
self.child_trx_list = TRXList()
# Tx (L12TRX) burst queue and mutex
self._tx_queue_lock = threading.Lock()
self._tx_queue = []
def __str__(self):
desc = "%s:%d" % (self.remote_addr, self.base_port)
if self.child_idx > 0:
desc += "/%d" % self.child_idx
if self.name is not None:
desc = "%s@%s" % (self.name, desc)
return desc
@property
def ready(self):
# Make sure that either both Rx/Tx frequencies are set
if self._rx_freq is None or self._tx_freq is None:
# ... or frequency hopping is in use
if self.fh is None:
return False
return True
def get_rx_freq(self, fn):
if self.fh is None:
return self._rx_freq
# Frequency hopping in use, resolve by TDMA fn
(rx_freq, _) = self.fh.resolve(fn)
return rx_freq
def get_tx_freq(self, fn):
if self.fh is None:
return self._tx_freq
# Frequency hopping in use, resolve by TDMA fn
(_, tx_freq) = self.fh.resolve(fn)
return tx_freq
def enable_fh(self, *args):
self.fh = HoppingParams(*args)
log.info("(%s) Frequency hopping configured: %s" % (self, self.fh))
def disable_fh(self):
if self.fh is not None:
log.info("(%s) Frequency hopping disabled" % self)
self.fh = None
# To be overwritten if required,
# no custom command handlers by default
def ctrl_cmd_handler(self, request):
return None
def power_event_handler(self, event):
# Update child transceivers
for trx in self.child_trx_list.trx_list:
if event == "POWERON":
trx.running = True
elif event == "POWEROFF":
trx.running = False
trx.tx_queue_clear()
trx.disable_fh()
# Reset frequency hopping parameters, clear the queue
if event == "POWEROFF":
self.tx_queue_clear()
self.disable_fh()
# Trigger clock generator if required
if self.clck_gen is not None:
clck_links = self.clck_gen.clck_links
if not self.running and (self.clck_if in clck_links):
# Transceiver was stopped
clck_links.remove(self.clck_if)
elif self.running and (self.clck_if not in clck_links):
# Transceiver was started
clck_links.append(self.clck_if)
if not self.clck_gen.running and len(clck_links) > 0:
log.info("Starting clock generator")
self.clck_gen.start()
elif self.clck_gen.running and not clck_links:
log.info("Stopping clock generator")
self.clck_gen.stop()
def recv_data_msg(self):
# Read and parse data from socket
msg = self.data_if.recv_l12trx_msg()
if not msg:
return None
# Make sure that transceiver is configured and running
if not self.running:
log.warning("(%s) RX TRXD message (%s), but transceiver "
"is not running => dropping..." % (self, msg.desc_hdr()))
return None
# Make sure that indicated timeslot is configured
if msg.tn not in self.ts_list:
log.warning("(%s) RX TRXD message (%s), but timeslot is not "
"configured => dropping..." % (self, msg.desc_hdr()))
return None
# Enque the message, it will be sent later
self.tx_queue_append(msg)
return msg
def handle_data_msg(self, msg):
# TODO: make legacy mode configurable (via argv?)
self.data_if.send_msg(msg, legacy = True)
def tx_queue_append(self, msg):
with self._tx_queue_lock:
self._tx_queue.append(msg)
def tx_queue_clear(self):
with self._tx_queue_lock:
# TODO: Python3: self._tx_queue.clear()
del self._tx_queue[:]
def clck_tick(self, fwd, fn):
if not self.running:
return
self._tx_queue_lock.acquire()
for msg in self._tx_queue:
if msg.fn == fn:
fwd.forward_msg(self, msg)
elif msg.fn < fn:
log.warning("(%s) Stale TRXD message (fn=%u): %s"
% (self, fn, msg.desc_hdr()))
self._tx_queue = [msg for msg in self._tx_queue if msg.fn > fn]
self._tx_queue_lock.release()
|