trxcon/l1sched: clarify TDMA Fn (mod 26) maps
[osmocom-bb.git] / src / target / trx_toolkit / transceiver.py
blobffd18abd6bf82dd6dc6d5b63a2b3540714a451b6
1 # -*- coding: utf-8 -*-
3 # TRX Toolkit
4 # Transceiver implementation
6 # (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
7 # Contributions by sysmocom - s.f.m.c. GmbH
9 # All Rights Reserved
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.
21 import logging as log
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
30 class Transceiver:
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.
46 == Child transceivers
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.
56 ...
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):
115 # Connection info
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)
122 # Meta info
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)
151 # Internal state
152 self.running = False
154 # Actual RX / TX frequencies
155 self._rx_freq = None
156 self._tx_freq = None
158 # Frequency hopping parameters (set by CTRL)
159 self.fh = None
161 # List of child transceivers
162 self.child_trx_list = TRXList()
164 def __str__(self):
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)
171 return desc
173 @property
174 def ready(self):
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
178 if self.fh is None:
179 return False
181 return True
183 def get_rx_freq(self, fn):
184 if self.fh is None:
185 return self._rx_freq
187 # Frequency hopping in use, resolve by TDMA fn
188 (rx_freq, _) = self.fh.resolve(fn)
189 return rx_freq
191 def get_tx_freq(self, fn):
192 if self.fh is None:
193 return self._tx_freq
195 # Frequency hopping in use, resolve by TDMA fn
196 (_, tx_freq) = self.fh.resolve(fn)
197 return tx_freq
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)
206 self.fh = None
208 # To be overwritten if required,
209 # no custom command handlers by default
210 def ctrl_cmd_handler(self, request):
211 return None
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]
217 else:
218 trx_list = [self]
219 # Update self and optionally child transceivers
220 for trx in trx_list:
221 trx.running = poweron
222 if not poweron:
223 trx.disable_fh()
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")
240 self.clck_gen.stop()
242 def recv_data_msg(self):
243 # Read and parse data from socket
244 msg = self.data_if.recv_tx_msg()
245 if not msg:
246 return None
248 # Make sure that transceiver is configured and running
249 if not self.running:
250 log.warning("(%s) RX TRXD message (%s), but transceiver "
251 "is not running => dropping..." % (self, msg.desc_hdr()))
252 return None
254 return msg
256 def handle_data_msg(self, msg):
257 # TODO: make legacy mode configurable (via argv?)
258 self.data_if.send_msg(msg, legacy = True)