1 # -*- coding: utf-8 -*-
4 # Transceiver implementation
6 # (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
7 # Contributions by sysmocom - s.f.m.c. GmbH
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
23 from ctrl_if_trx
import CTRLInterfaceTRX
24 from data_if
import DATAInterface
25 from udp_link
import UDPLink
26 from trx_list
import TRXList
28 from gsm_shared
import HoppingParams
31 """ Base transceiver implementation.
33 Represents a single transceiver, that can be used as for the BTS side,
34 as for the MS side. Each individual instance of Transceiver unifies
35 three basic interfaces built on three independent UDP connections:
37 - CLCK (base port + 100/0) - clock indications from TRX to L1,
38 - CTRL (base port + 101/1) - control interface for L1,
39 - DATA (base port + 102/2) - bidirectional data interface for bursts.
41 A transceiver can be either in active (i.e. working), or in idle mode.
42 The active mode should ensure that both RX/TX frequencies are set.
44 NOTE: CLCK is not required for some L1 implementations, so it is optional.
48 A BTS can (optionally) have more than one transceiver. In this case
49 additional (let's say child) transceivers basically share the same
50 clock source of the first transceiver, so UDP port mapping is a bit
51 different, for example:
53 (trx_0) clck=5700, ctrl=5701, data=5702,
54 (trx_1) ctrl=5703, data=5704,
55 (trx_2) ctrl=5705, data=5706.
58 By default, powering on/off a parent transceiver (child_idx=0) will
59 automatically power on/off its child transceivers (if any). This
60 behavior can be disabled by setting "child_mgt" param to False.
62 == Clock distribution (optional)
64 The clock indications are not expected by L1 when transceiver
65 is not running, so we monitor both POWERON / POWEROFF events
66 from the control interface, and keep the list of CLCK links
67 in a given CLCKGen instance updated. The clock generator is
68 started and stopped automatically.
70 NOTE: a single instance of CLCKGen can be shared between multiple
71 transceivers, as well as multiple transceivers may use
72 individual CLCKGen instances.
74 == Power Measurement (optional)
76 Transceiver may have an optional power measurement interface,
77 that shall provide at least one method: measure(freq). This
78 is required for the MS side (i.e. OsmocomBB).
80 == Frequency hopping (optional)
82 There are two ways to implement frequency hopping:
84 a) The Transceiver is configured with the hopping parameters, in
85 particular HSN, MAIO, and the list of ARFCNs (channels), so the
86 actual Rx/Tx frequencies are changed by the Transceiver itself
87 depending on the current TDMA frame number.
89 b) The L1 maintains several Transceivers (two or more), so each
90 instance is assigned one dedicated RF carrier frequency, and
91 hence the number of available hopping frequencies is equal to
92 the number of Transceivers. In this case, it's the task of
93 the L1 to commutate bursts between Transceivers (frequencies).
95 Variant a) is commonly known as "synthesizer frequency hopping"
96 whereas b) is known as "baseband frequency hopping".
98 For the MS side, a) is preferred, because a phone usually has only
99 one Transceiver (per RAT). On the other hand, b) is more suitable
100 for the BTS side, because it's relatively easy to implement and
101 there is no technical limitation on the amount of Transceivers.
103 FakeTRX obviously does support b) since multi-TRX feature has been
104 implemented, as well as a) by resolving UL/DL frequencies using a
105 preconfigured (by the L1) set of the hopping parameters. The later
106 can be enabled using the SETFH control command.
108 NOTE: in the current implementation, mode a) applies to the whole
109 Transceiver and all its timeslots, so using in for the BTS side
110 does not make any sense (imagine BCCH hopping together with DCCH).
114 def __init__(self
, bind_addr
, remote_addr
, base_port
, **kwargs
):
116 self
.remote_addr
= remote_addr
117 self
.bind_addr
= bind_addr
118 self
.base_port
= base_port
119 self
.child_idx
= kwargs
.get("child_idx", 0)
120 self
.child_mgt
= kwargs
.get("child_mgt", True)
123 self
.name
= kwargs
.get("name", None)
125 log
.info("Init transceiver '%s'" % self
)
127 # Child transceiver cannot have its own clock
128 self
.clck_gen
= kwargs
.get("clck_gen", None)
129 if self
.clck_gen
is not None and self
.child_idx
> 0:
130 raise TypeError("Child transceiver cannot have its own clock")
132 # Init DATA interface
133 self
.data_if
= DATAInterface(
134 remote_addr
, base_port
+ self
.child_idx
* 2 + 102,
135 bind_addr
, base_port
+ self
.child_idx
* 2 + 2)
137 # Init CTRL interface
138 self
.ctrl_if
= CTRLInterfaceTRX(self
,
139 remote_addr
, base_port
+ self
.child_idx
* 2 + 101,
140 bind_addr
, base_port
+ self
.child_idx
* 2 + 1)
142 # Init optional CLCK interface
143 if self
.clck_gen
is not None:
144 self
.clck_if
= UDPLink(
145 remote_addr
, base_port
+ 100,
146 bind_addr
, base_port
)
148 # Optional Power Measurement interface
149 self
.pwr_meas
= kwargs
.get("pwr_meas", None)
154 # Actual RX / TX frequencies
158 # Frequency hopping parameters (set by CTRL)
161 # List of child transceivers
162 self
.child_trx_list
= TRXList()
165 desc
= "%s:%d" % (self
.remote_addr
, self
.base_port
)
166 if self
.child_idx
> 0:
167 desc
+= "/%d" % self
.child_idx
168 if self
.name
is not None:
169 desc
= "%s@%s" % (self
.name
, desc
)
175 # Make sure that either both Rx/Tx frequencies are set
176 if self
._rx
_freq
is None or self
._tx
_freq
is None:
177 # ... or frequency hopping is in use
183 def get_rx_freq(self
, fn
):
187 # Frequency hopping in use, resolve by TDMA fn
188 (rx_freq
, _
) = self
.fh
.resolve(fn
)
191 def get_tx_freq(self
, fn
):
195 # Frequency hopping in use, resolve by TDMA fn
196 (_
, tx_freq
) = self
.fh
.resolve(fn
)
199 def enable_fh(self
, *args
):
200 self
.fh
= HoppingParams(*args
)
201 log
.info("(%s) Frequency hopping configured: %s" % (self
, self
.fh
))
203 def disable_fh(self
):
204 if self
.fh
is not None:
205 log
.info("(%s) Frequency hopping disabled" % self
)
208 # To be overwritten if required,
209 # no custom command handlers by default
210 def ctrl_cmd_handler(self
, request
):
213 def power_event_handler(self
, poweron
: bool) -> None:
214 # If self.child_mgt is True, automatically power on/off children
215 if self
.child_mgt
and self
.child_idx
== 0:
216 trx_list
= [self
, *self
.child_trx_list
.trx_list
]
219 # Update self and optionally child transceivers
221 trx
.running
= poweron
225 # Trigger clock generator if required
226 if self
.clck_gen
is not None:
227 clck_links
= self
.clck_gen
.clck_links
228 if not self
.running
and (self
.clck_if
in clck_links
):
229 # Transceiver was stopped
230 clck_links
.remove(self
.clck_if
)
231 elif self
.running
and (self
.clck_if
not in clck_links
):
232 # Transceiver was started
233 clck_links
.append(self
.clck_if
)
235 if not self
.clck_gen
.running
and len(clck_links
) > 0:
236 log
.info("Starting clock generator")
237 self
.clck_gen
.start()
238 elif self
.clck_gen
.running
and not clck_links
:
239 log
.info("Stopping clock generator")
242 def recv_data_msg(self
):
243 # Read and parse data from socket
244 msg
= self
.data_if
.recv_tx_msg()
248 # Make sure that transceiver is configured and running
250 log
.warning("(%s) RX TRXD message (%s), but transceiver "
251 "is not running => dropping..." % (self
, msg
.desc_hdr()))
256 def handle_data_msg(self
, msg
):
257 # TODO: make legacy mode configurable (via argv?)
258 self
.data_if
.send_msg(msg
, legacy
= True)