blob: a6a32f0707ad3e9fea236d894cb67f6d8019ba7a [file] [log] [blame]
dburgessb3a0ca42011-10-12 07:44:40 +00001/*
2* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
3*
4* This software is distributed under the terms of the GNU Public License.
5* See the COPYING file in the main directory for details.
6*
7* This use of this software may be subject to additional restrictions.
8* See the LEGAL file in the main directory for details.
9
10 This program is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>.
22*/
23
dburgessb3a0ca42011-10-12 07:44:40 +000024#include <stdio.h>
Alexander Chemerise8905a02015-06-03 23:47:56 -040025#include <iomanip> // std::setprecision
Alexander Chemerise692ce92015-06-12 00:15:31 -040026#include <fstream>
dburgessb3a0ca42011-10-12 07:44:40 +000027#include "Transceiver.h"
28#include <Logger.h>
29
Pau Espin Pedroldb936b92018-09-03 16:50:49 +020030extern "C" {
31#include "osmo_signal.h"
Pau Espin Pedrol778b30a2019-06-28 13:27:24 +020032#include "proto_trxd.h"
33
34#include <osmocom/core/bits.h>
Pau Espin Pedroldb936b92018-09-03 16:50:49 +020035}
36
ttsou2173abf2012-08-08 00:51:31 +000037#ifdef HAVE_CONFIG_H
38#include "config.h"
39#endif
dburgessb3a0ca42011-10-12 07:44:40 +000040
Alexander Chemerisd734e2d2013-06-16 14:30:58 +040041using namespace GSM;
42
kurtis.heimerlec842de2012-11-23 08:37:32 +000043#define USB_LATENCY_INTRVL 10,0
ttsou2173abf2012-08-08 00:51:31 +000044
Thomas Tsoufa3a7872013-10-17 21:23:34 -040045/* Number of running values use in noise average */
46#define NOISE_CNT 20
ttsoue8dde022012-12-06 15:43:55 +000047
Thomas Tsouf0782732013-10-29 15:55:47 -040048TransceiverState::TransceiverState()
Tom Tsoua4d1a412014-11-25 15:46:56 -080049 : mRetrans(false), mNoiseLev(0.0), mNoises(NOISE_CNT), mPower(0.0)
Thomas Tsouf0782732013-10-29 15:55:47 -040050{
51 for (int i = 0; i < 8; i++) {
52 chanType[i] = Transceiver::NONE;
53 fillerModulus[i] = 26;
54 chanResponse[i] = NULL;
55 DFEForward[i] = NULL;
56 DFEFeedback[i] = NULL;
57
58 for (int n = 0; n < 102; n++)
59 fillerTable[n][i] = NULL;
60 }
61}
62
63TransceiverState::~TransceiverState()
64{
65 for (int i = 0; i < 8; i++) {
66 delete chanResponse[i];
67 delete DFEForward[i];
68 delete DFEFeedback[i];
69
70 for (int n = 0; n < 102; n++)
71 delete fillerTable[n][i];
72 }
73}
74
Pau Espin Pedrolefac20b2018-02-21 14:59:19 +010075bool TransceiverState::init(FillerType filler, size_t sps, float scale, size_t rtsc, unsigned rach_delay)
Tom Tsou64ad7122015-05-19 18:26:31 -070076{
Tom Tsou64ad7122015-05-19 18:26:31 -070077 signalVector *burst;
78
79 if ((sps != 1) && (sps != 4))
80 return false;
81
82 for (size_t n = 0; n < 8; n++) {
Tom Tsou64ad7122015-05-19 18:26:31 -070083 for (size_t i = 0; i < 102; i++) {
84 switch (filler) {
Pau Espin Pedrolefac20b2018-02-21 14:59:19 +010085 case FILLER_DUMMY:
Tom Tsou8ee2f382016-03-06 20:57:34 -080086 burst = generateDummyBurst(sps, n);
Tom Tsou64ad7122015-05-19 18:26:31 -070087 break;
Pau Espin Pedrolefac20b2018-02-21 14:59:19 +010088 case FILLER_NORM_RAND:
Tom Tsou8ee2f382016-03-06 20:57:34 -080089 burst = genRandNormalBurst(rtsc, sps, n);
Tom Tsou64ad7122015-05-19 18:26:31 -070090 break;
Pau Espin Pedrolefac20b2018-02-21 14:59:19 +010091 case FILLER_EDGE_RAND:
Tom Tsouaf717b22016-03-06 22:19:15 -080092 burst = generateEdgeBurst(rtsc);
93 break;
Pau Espin Pedrolefac20b2018-02-21 14:59:19 +010094 case FILLER_ACCESS_RAND:
Alexander Chemeris37c52c72016-03-25 18:28:34 +030095 burst = genRandAccessBurst(rach_delay, sps, n);
Alexander Chemeris5efe0502016-03-23 17:06:32 +030096 break;
Pau Espin Pedrolefac20b2018-02-21 14:59:19 +010097 case FILLER_ZERO:
Tom Tsou64ad7122015-05-19 18:26:31 -070098 default:
Tom Tsou8ee2f382016-03-06 20:57:34 -080099 burst = generateEmptyBurst(sps, n);
Tom Tsou64ad7122015-05-19 18:26:31 -0700100 }
101
102 scaleVector(*burst, scale);
103 fillerTable[i][n] = burst;
104 }
105
Pau Espin Pedrolefac20b2018-02-21 14:59:19 +0100106 if ((filler == FILLER_NORM_RAND) ||
107 (filler == FILLER_EDGE_RAND)) {
Alexander Chemerisf9e78be2017-03-17 15:00:34 -0700108 chanType[n] = TSC;
Tom Tsouaf717b22016-03-06 22:19:15 -0800109 }
Thomas Tsou15d743e2014-01-25 02:34:03 -0500110 }
Tom Tsou64ad7122015-05-19 18:26:31 -0700111
112 return false;
Thomas Tsouf0782732013-10-29 15:55:47 -0400113}
114
dburgessb3a0ca42011-10-12 07:44:40 +0000115Transceiver::Transceiver(int wBasePort,
Pau Espin Pedrol8c800952017-08-16 16:53:23 +0200116 const char *TRXAddress,
117 const char *GSMcoreAddress,
Tom Tsou5cd70dc2016-03-06 01:28:40 -0800118 size_t tx_sps, size_t rx_sps, size_t chans,
Alexander Chemerise8905a02015-06-03 23:47:56 -0400119 GSM::Time wTransmitLatency,
120 RadioInterface *wRadioInterface,
Eric Wildac0487e2019-06-17 13:02:44 +0200121 double wRssiOffset, int wStackSize)
Pau Espin Pedrol8c800952017-08-16 16:53:23 +0200122 : mBasePort(wBasePort), mLocalAddr(TRXAddress), mRemoteAddr(GSMcoreAddress),
123 mClockSocket(TRXAddress, wBasePort, GSMcoreAddress, wBasePort + 100),
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800124 mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface),
Eric Wildac0487e2019-06-17 13:02:44 +0200125 rssiOffset(wRssiOffset), stackSize(wStackSize),
Pau Espin Pedrol934da482017-07-04 16:25:20 +0200126 mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mEdge(false), mOn(false), mForceClockInterface(false),
Alexander Chemeris78d1fc92016-03-19 21:16:22 +0300127 mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), mMaxExpectedDelayNB(0),
128 mWriteBurstToDiskMask(0)
dburgessb3a0ca42011-10-12 07:44:40 +0000129{
dburgessb3a0ca42011-10-12 07:44:40 +0000130 txFullScale = mRadioInterface->fullScaleInputValue();
131 rxFullScale = mRadioInterface->fullScaleOutputValue();
Alexander Chemeris5a068062015-06-20 01:38:47 +0300132
133 for (int i = 0; i < 8; i++) {
134 for (int j = 0; j < 8; j++)
135 mHandover[i][j] = false;
136 }
dburgessb3a0ca42011-10-12 07:44:40 +0000137}
138
139Transceiver::~Transceiver()
140{
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800141 stop();
142
dburgessb3a0ca42011-10-12 07:44:40 +0000143 sigProcLibDestroy();
Thomas Tsoud647ec52013-10-29 15:17:34 -0400144
Thomas Tsou204a9f12013-10-29 18:34:16 -0400145 for (size_t i = 0; i < mChans; i++) {
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800146 mControlServiceLoopThreads[i]->cancel();
147 mControlServiceLoopThreads[i]->join();
148 delete mControlServiceLoopThreads[i];
149
Thomas Tsou204a9f12013-10-29 18:34:16 -0400150 mTxPriorityQueues[i].clear();
151 delete mCtrlSockets[i];
152 delete mDataSockets[i];
153 }
dburgessb3a0ca42011-10-12 07:44:40 +0000154}
Thomas Tsou83e06892013-08-20 16:10:01 -0400155
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800156/*
157 * Initialize transceiver
158 *
159 * Start or restart the control loop. Any further control is handled through the
160 * socket API. Randomize the central radio clock set the downlink burst
161 * counters. Note that the clock will not update until the radio starts, but we
162 * are still expected to report clock indications through control channel
163 * activity.
164 */
Vadim Yanitskiya8b35652018-10-22 02:52:18 +0200165bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
166 bool edge, bool ext_rach)
Thomas Tsou83e06892013-08-20 16:10:01 -0400167{
Thomas Tsoue1ce9252013-11-13 22:40:44 -0500168 int d_srcport, d_dstport, c_srcport, c_dstport;
Thomas Tsouf0782732013-10-29 15:55:47 -0400169
Thomas Tsou204a9f12013-10-29 18:34:16 -0400170 if (!mChans) {
171 LOG(ALERT) << "No channels assigned";
172 return false;
173 }
174
Tom Tsou2079a3c2016-03-06 00:58:56 -0800175 if (!sigProcLibSetup()) {
Thomas Tsou83e06892013-08-20 16:10:01 -0400176 LOG(ALERT) << "Failed to initialize signal processing library";
177 return false;
178 }
179
Vadim Yanitskiya8b35652018-10-22 02:52:18 +0200180 mExtRACH = ext_rach;
Tom Tsou64464e62016-07-01 03:46:46 -0700181 mEdge = edge;
182
Thomas Tsou204a9f12013-10-29 18:34:16 -0400183 mDataSockets.resize(mChans);
184 mCtrlSockets.resize(mChans);
Thomas Tsou204a9f12013-10-29 18:34:16 -0400185 mControlServiceLoopThreads.resize(mChans);
186 mTxPriorityQueueServiceLoopThreads.resize(mChans);
187 mRxServiceLoopThreads.resize(mChans);
188
189 mTxPriorityQueues.resize(mChans);
190 mReceiveFIFO.resize(mChans);
191 mStates.resize(mChans);
192
Thomas Tsouccb73e12014-04-15 17:41:28 -0400193 /* Filler table retransmissions - support only on channel 0 */
Tom Tsou64ad7122015-05-19 18:26:31 -0700194 if (filler == FILLER_DUMMY)
Thomas Tsouccb73e12014-04-15 17:41:28 -0400195 mStates[0].mRetrans = true;
196
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800197 /* Setup sockets */
Thomas Tsou204a9f12013-10-29 18:34:16 -0400198 for (size_t i = 0; i < mChans; i++) {
Thomas Tsoue1ce9252013-11-13 22:40:44 -0500199 c_srcport = mBasePort + 2 * i + 1;
200 c_dstport = mBasePort + 2 * i + 101;
201 d_srcport = mBasePort + 2 * i + 2;
202 d_dstport = mBasePort + 2 * i + 102;
203
Pau Espin Pedrol8c800952017-08-16 16:53:23 +0200204 mCtrlSockets[i] = new UDPSocket(mLocalAddr.c_str(), c_srcport, mRemoteAddr.c_str(), c_dstport);
205 mDataSockets[i] = new UDPSocket(mLocalAddr.c_str(), d_srcport, mRemoteAddr.c_str(), d_dstport);
Thomas Tsou204a9f12013-10-29 18:34:16 -0400206 }
Thomas Tsou83e06892013-08-20 16:10:01 -0400207
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800208 /* Randomize the central clock */
209 GSM::Time startTime(random() % gHyperframe, 0);
210 mRadioInterface->getClock()->set(startTime);
211 mTransmitDeadlineClock = startTime;
212 mLastClockUpdateTime = startTime;
213 mLatencyUpdateTime = startTime;
214
215 /* Start control threads */
Thomas Tsou204a9f12013-10-29 18:34:16 -0400216 for (size_t i = 0; i < mChans; i++) {
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800217 TransceiverChannel *chan = new TransceiverChannel(this, i);
Eric Wildac0487e2019-06-17 13:02:44 +0200218 mControlServiceLoopThreads[i] = new Thread(stackSize);
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800219 mControlServiceLoopThreads[i]->start((void * (*)(void*))
220 ControlServiceLoopAdapter, (void*) chan);
Thomas Tsou204a9f12013-10-29 18:34:16 -0400221
Tom Tsou64ad7122015-05-19 18:26:31 -0700222 if (i && filler == FILLER_DUMMY)
223 filler = FILLER_ZERO;
224
Alexander Chemeris37c52c72016-03-25 18:28:34 +0300225 mStates[i].init(filler, mSPSTx, txFullScale, rtsc, rach_delay);
Thomas Tsou83e06892013-08-20 16:10:01 -0400226 }
227
228 return true;
229}
dburgessb3a0ca42011-10-12 07:44:40 +0000230
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800231/*
232 * Start the transceiver
233 *
234 * Submit command(s) to the radio device to commence streaming samples and
235 * launch threads to handle sample I/O. Re-synchronize the transmit burst
236 * counters to the central radio clock here as well.
237 */
238bool Transceiver::start()
239{
240 ScopedLock lock(mLock);
241
242 if (mOn) {
243 LOG(ERR) << "Transceiver already running";
Ivan Kluchnikov194a9b12015-04-23 17:08:27 +0300244 return true;
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800245 }
246
247 LOG(NOTICE) << "Starting the transceiver";
248
249 GSM::Time time = mRadioInterface->getClock()->get();
250 mTransmitDeadlineClock = time;
251 mLastClockUpdateTime = time;
252 mLatencyUpdateTime = time;
253
254 if (!mRadioInterface->start()) {
255 LOG(ALERT) << "Device failed to start";
256 return false;
257 }
258
259 /* Device is running - launch I/O threads */
Eric Wildac0487e2019-06-17 13:02:44 +0200260 mRxLowerLoopThread = new Thread(stackSize);
261 mTxLowerLoopThread = new Thread(stackSize);
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800262 mTxLowerLoopThread->start((void * (*)(void*))
263 TxLowerLoopAdapter,(void*) this);
264 mRxLowerLoopThread->start((void * (*)(void*))
265 RxLowerLoopAdapter,(void*) this);
266
267 /* Launch uplink and downlink burst processing threads */
268 for (size_t i = 0; i < mChans; i++) {
269 TransceiverChannel *chan = new TransceiverChannel(this, i);
Eric Wildac0487e2019-06-17 13:02:44 +0200270 mRxServiceLoopThreads[i] = new Thread(stackSize);
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800271 mRxServiceLoopThreads[i]->start((void * (*)(void*))
272 RxUpperLoopAdapter, (void*) chan);
273
274 chan = new TransceiverChannel(this, i);
Eric Wildac0487e2019-06-17 13:02:44 +0200275 mTxPriorityQueueServiceLoopThreads[i] = new Thread(stackSize);
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800276 mTxPriorityQueueServiceLoopThreads[i]->start((void * (*)(void*))
277 TxUpperLoopAdapter, (void*) chan);
278 }
279
Pau Espin Pedrol934da482017-07-04 16:25:20 +0200280 mForceClockInterface = true;
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800281 mOn = true;
282 return true;
283}
284
285/*
286 * Stop the transceiver
287 *
288 * Perform stopping by disabling receive streaming and issuing cancellation
289 * requests to running threads. Most threads will timeout and terminate once
290 * device is disabled, but the transmit loop may block waiting on the central
291 * UMTS clock. Explicitly signal the clock to make sure that the transmit loop
292 * makes it to the thread cancellation point.
293 */
294void Transceiver::stop()
295{
296 ScopedLock lock(mLock);
297
298 if (!mOn)
299 return;
300
301 LOG(NOTICE) << "Stopping the transceiver";
302 mTxLowerLoopThread->cancel();
303 mRxLowerLoopThread->cancel();
Tom Tsoud67bd602017-06-15 15:35:02 -0700304 mTxLowerLoopThread->join();
305 mRxLowerLoopThread->join();
306 delete mTxLowerLoopThread;
307 delete mRxLowerLoopThread;
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800308
309 for (size_t i = 0; i < mChans; i++) {
310 mRxServiceLoopThreads[i]->cancel();
311 mTxPriorityQueueServiceLoopThreads[i]->cancel();
312 }
313
314 LOG(INFO) << "Stopping the device";
315 mRadioInterface->stop();
316
317 for (size_t i = 0; i < mChans; i++) {
318 mRxServiceLoopThreads[i]->join();
319 mTxPriorityQueueServiceLoopThreads[i]->join();
320 delete mRxServiceLoopThreads[i];
321 delete mTxPriorityQueueServiceLoopThreads[i];
322
323 mTxPriorityQueues[i].clear();
324 }
325
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800326 mOn = false;
327 LOG(NOTICE) << "Transceiver stopped";
328}
329
Thomas Tsoua2fe91a2013-11-13 22:48:11 -0500330void Transceiver::addRadioVector(size_t chan, BitVector &bits,
Thomas Tsou204a9f12013-10-29 18:34:16 -0400331 int RSSI, GSM::Time &wTime)
dburgessb3a0ca42011-10-12 07:44:40 +0000332{
Thomas Tsoua2fe91a2013-11-13 22:48:11 -0500333 signalVector *burst;
334 radioVector *radio_burst;
335
Thomas Tsou204a9f12013-10-29 18:34:16 -0400336 if (chan >= mTxPriorityQueues.size()) {
337 LOG(ALERT) << "Invalid channel " << chan;
338 return;
339 }
340
Thomas Tsou2d0c00b2013-11-14 15:28:23 -0500341 if (wTime.TN() > 7) {
342 LOG(ALERT) << "Received burst with invalid slot " << wTime.TN();
343 return;
344 }
345
Tom Tsoub0aefcb2016-03-06 03:44:34 -0800346 /* Use the number of bits as the EDGE burst indicator */
347 if (bits.size() == EDGE_BURST_NBITS)
348 burst = modulateEdgeBurst(bits, mSPSTx);
349 else
350 burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), mSPSTx);
351
Thomas Tsoua2fe91a2013-11-13 22:48:11 -0500352 scaleVector(*burst, txFullScale * pow(10, -RSSI / 10));
dburgessb3a0ca42011-10-12 07:44:40 +0000353
Thomas Tsoua2fe91a2013-11-13 22:48:11 -0500354 radio_burst = new radioVector(wTime, burst);
355
356 mTxPriorityQueues[chan].write(radio_burst);
dburgessb3a0ca42011-10-12 07:44:40 +0000357}
358
Thomas Tsou15d743e2014-01-25 02:34:03 -0500359void Transceiver::updateFillerTable(size_t chan, radioVector *burst)
360{
361 int TN, modFN;
362 TransceiverState *state = &mStates[chan];
363
364 TN = burst->getTime().TN();
365 modFN = burst->getTime().FN() % state->fillerModulus[TN];
366
367 delete state->fillerTable[modFN][TN];
368 state->fillerTable[modFN][TN] = burst->getVector();
369 burst->setVector(NULL);
370}
371
dburgessb3a0ca42011-10-12 07:44:40 +0000372void Transceiver::pushRadioVector(GSM::Time &nowTime)
373{
Thomas Tsou204a9f12013-10-29 18:34:16 -0400374 int TN, modFN;
375 radioVector *burst;
376 TransceiverState *state;
377 std::vector<signalVector *> bursts(mChans);
378 std::vector<bool> zeros(mChans);
Thomas Tsou15d743e2014-01-25 02:34:03 -0500379 std::vector<bool> filler(mChans, true);
dburgessb3a0ca42011-10-12 07:44:40 +0000380
Thomas Tsou204a9f12013-10-29 18:34:16 -0400381 for (size_t i = 0; i < mChans; i ++) {
382 state = &mStates[i];
dburgessb3a0ca42011-10-12 07:44:40 +0000383
Thomas Tsou204a9f12013-10-29 18:34:16 -0400384 while ((burst = mTxPriorityQueues[i].getStaleBurst(nowTime))) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200385 LOGCHAN(i, DMAIN, NOTICE) << "dumping STALE burst in TRX->SDR interface ("
Pau Espin Pedrolf37b0ad2018-04-25 18:01:27 +0200386 << burst->getTime() <<" vs " << nowTime << "), retrans=" << state->mRetrans;
Thomas Tsou15d743e2014-01-25 02:34:03 -0500387 if (state->mRetrans)
388 updateFillerTable(i, burst);
Thomas Tsoua2fe91a2013-11-13 22:48:11 -0500389 delete burst;
Thomas Tsou204a9f12013-10-29 18:34:16 -0400390 }
391
392 TN = nowTime.TN();
393 modFN = nowTime.FN() % state->fillerModulus[TN];
394
395 bursts[i] = state->fillerTable[modFN][TN];
396 zeros[i] = state->chanType[TN] == NONE;
397
398 if ((burst = mTxPriorityQueues[i].getCurrentBurst(nowTime))) {
Thomas Tsoua2fe91a2013-11-13 22:48:11 -0500399 bursts[i] = burst->getVector();
Thomas Tsou15d743e2014-01-25 02:34:03 -0500400
401 if (state->mRetrans) {
402 updateFillerTable(i, burst);
403 } else {
404 burst->setVector(NULL);
405 filler[i] = false;
406 }
407
Thomas Tsoua2fe91a2013-11-13 22:48:11 -0500408 delete burst;
Thomas Tsou204a9f12013-10-29 18:34:16 -0400409 }
dburgessb3a0ca42011-10-12 07:44:40 +0000410 }
411
Thomas Tsou204a9f12013-10-29 18:34:16 -0400412 mRadioInterface->driveTransmitRadio(bursts, zeros);
413
Thomas Tsou15d743e2014-01-25 02:34:03 -0500414 for (size_t i = 0; i < mChans; i++) {
415 if (!filler[i])
416 delete bursts[i];
417 }
dburgessb3a0ca42011-10-12 07:44:40 +0000418}
419
Thomas Tsou204a9f12013-10-29 18:34:16 -0400420void Transceiver::setModulus(size_t timeslot, size_t chan)
dburgessb3a0ca42011-10-12 07:44:40 +0000421{
Thomas Tsou204a9f12013-10-29 18:34:16 -0400422 TransceiverState *state = &mStates[chan];
423
424 switch (state->chanType[timeslot]) {
dburgessb3a0ca42011-10-12 07:44:40 +0000425 case NONE:
426 case I:
427 case II:
428 case III:
429 case FILL:
Thomas Tsou204a9f12013-10-29 18:34:16 -0400430 state->fillerModulus[timeslot] = 26;
dburgessb3a0ca42011-10-12 07:44:40 +0000431 break;
432 case IV:
433 case VI:
434 case V:
Thomas Tsou204a9f12013-10-29 18:34:16 -0400435 state->fillerModulus[timeslot] = 51;
dburgessb3a0ca42011-10-12 07:44:40 +0000436 break;
Pau Espin Pedrol7c405a02017-07-04 16:24:06 +0200437 //case V:
dburgessb3a0ca42011-10-12 07:44:40 +0000438 case VII:
Thomas Tsou204a9f12013-10-29 18:34:16 -0400439 state->fillerModulus[timeslot] = 102;
dburgessb3a0ca42011-10-12 07:44:40 +0000440 break;
ttsoufc40a842013-06-09 22:38:18 +0000441 case XIII:
Thomas Tsou204a9f12013-10-29 18:34:16 -0400442 state->fillerModulus[timeslot] = 52;
ttsoufc40a842013-06-09 22:38:18 +0000443 break;
dburgessb3a0ca42011-10-12 07:44:40 +0000444 default:
445 break;
446 }
447}
448
449
Alexander Chemerisf9e78be2017-03-17 15:00:34 -0700450CorrType Transceiver::expectedCorrType(GSM::Time currTime,
451 size_t chan)
dburgessb3a0ca42011-10-12 07:44:40 +0000452{
Alexander Chemeris5a068062015-06-20 01:38:47 +0300453 static int tchh_subslot[26] = { 0,1,0,1,0,1,0,1,0,1,0,1,0,0,1,0,1,0,1,0,1,0,1,0,1,1 };
454 static int sdcch4_subslot[102] = { 3,3,3,3,0,0,2,2,2,2,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,2,2,2,2,
455 3,3,3,3,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,2,2,2,2 };
456 static int sdcch8_subslot[102] = { 5,5,5,5,6,6,6,6,7,7,7,7,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,0,0,0,0,
457 1,1,1,1,2,2,2,2,3,3,3,3,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,4,4,4,4 };
Thomas Tsou204a9f12013-10-29 18:34:16 -0400458 TransceiverState *state = &mStates[chan];
dburgessb3a0ca42011-10-12 07:44:40 +0000459 unsigned burstTN = currTime.TN();
460 unsigned burstFN = currTime.FN();
Alexander Chemeris5a068062015-06-20 01:38:47 +0300461 int subch;
dburgessb3a0ca42011-10-12 07:44:40 +0000462
Thomas Tsou204a9f12013-10-29 18:34:16 -0400463 switch (state->chanType[burstTN]) {
dburgessb3a0ca42011-10-12 07:44:40 +0000464 case NONE:
465 return OFF;
466 break;
467 case FILL:
468 return IDLE;
469 break;
470 case I:
Alexander Chemeris5a068062015-06-20 01:38:47 +0300471 // TODO: Are we expecting RACH on an IDLE frame?
472/* if (burstFN % 26 == 25)
473 return IDLE;*/
474 if (mHandover[burstTN][0])
475 return RACH;
dburgessb3a0ca42011-10-12 07:44:40 +0000476 return TSC;
dburgessb3a0ca42011-10-12 07:44:40 +0000477 break;
478 case II:
Alexander Chemeris5a068062015-06-20 01:38:47 +0300479 subch = tchh_subslot[burstFN % 26];
480 if (subch == 1)
481 return IDLE;
482 if (mHandover[burstTN][0])
483 return RACH;
ttsou20642972013-03-27 22:00:25 +0000484 return TSC;
dburgessb3a0ca42011-10-12 07:44:40 +0000485 break;
486 case III:
Alexander Chemeris5a068062015-06-20 01:38:47 +0300487 subch = tchh_subslot[burstFN % 26];
488 if (mHandover[burstTN][subch])
489 return RACH;
dburgessb3a0ca42011-10-12 07:44:40 +0000490 return TSC;
491 break;
492 case IV:
493 case VI:
Vadim Yanitskiya8b35652018-10-22 02:52:18 +0200494 return mExtRACH ? EXT_RACH : RACH;
dburgessb3a0ca42011-10-12 07:44:40 +0000495 break;
496 case V: {
497 int mod51 = burstFN % 51;
498 if ((mod51 <= 36) && (mod51 >= 14))
Vadim Yanitskiya8b35652018-10-22 02:52:18 +0200499 return mExtRACH ? EXT_RACH : RACH;
dburgessb3a0ca42011-10-12 07:44:40 +0000500 else if ((mod51 == 4) || (mod51 == 5))
Vadim Yanitskiya8b35652018-10-22 02:52:18 +0200501 return mExtRACH ? EXT_RACH : RACH;
dburgessb3a0ca42011-10-12 07:44:40 +0000502 else if ((mod51 == 45) || (mod51 == 46))
Vadim Yanitskiya8b35652018-10-22 02:52:18 +0200503 return mExtRACH ? EXT_RACH : RACH;
Alexander Chemeris5a068062015-06-20 01:38:47 +0300504 else if (mHandover[burstTN][sdcch4_subslot[burstFN % 102]])
505 return RACH;
dburgessb3a0ca42011-10-12 07:44:40 +0000506 else
507 return TSC;
508 break;
509 }
510 case VII:
511 if ((burstFN % 51 <= 14) && (burstFN % 51 >= 12))
512 return IDLE;
Alexander Chemeris5a068062015-06-20 01:38:47 +0300513 else if (mHandover[burstTN][sdcch8_subslot[burstFN % 102]])
514 return RACH;
dburgessb3a0ca42011-10-12 07:44:40 +0000515 else
516 return TSC;
517 break;
ttsoufc40a842013-06-09 22:38:18 +0000518 case XIII: {
519 int mod52 = burstFN % 52;
520 if ((mod52 == 12) || (mod52 == 38))
Vadim Yanitskiya8b35652018-10-22 02:52:18 +0200521 return mExtRACH ? EXT_RACH : RACH;
ttsoufc40a842013-06-09 22:38:18 +0000522 else if ((mod52 == 25) || (mod52 == 51))
523 return IDLE;
524 else
525 return TSC;
526 break;
527 }
dburgessb3a0ca42011-10-12 07:44:40 +0000528 case LOOPBACK:
529 if ((burstFN % 51 <= 50) && (burstFN % 51 >=48))
530 return IDLE;
531 else
532 return TSC;
533 break;
534 default:
535 return OFF;
536 break;
537 }
dburgessb3a0ca42011-10-12 07:44:40 +0000538}
Thomas Tsoufa3a7872013-10-17 21:23:34 -0400539
Alexander Chemerise692ce92015-06-12 00:15:31 -0400540void writeToFile(radioVector *radio_burst, size_t chan)
541{
542 GSM::Time time = radio_burst->getTime();
543 std::ostringstream fname;
544 fname << chan << "_" << time.FN() << "_" << time.TN() << ".fc";
545 std::ofstream outfile (fname.str().c_str(), std::ofstream::binary);
546 outfile.write((char*)radio_burst->getVector()->begin(), radio_burst->getVector()->size() * 2 * sizeof(float));
547 outfile.close();
548}
549
Thomas Tsou30421a72013-11-13 23:14:48 -0500550/*
551 * Pull bursts from the FIFO and handle according to the slot
Pau Espin Pedrol7c405a02017-07-04 16:24:06 +0200552 * and burst correlation type. Equalzation is currently disabled.
Thomas Tsou30421a72013-11-13 23:14:48 -0500553 */
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200554bool Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
dburgessb3a0ca42011-10-12 07:44:40 +0000555{
Tom Tsoub0aefcb2016-03-06 03:44:34 -0800556 int rc;
Thomas Tsou30421a72013-11-13 23:14:48 -0500557 complex amp;
Alexander Chemeris1dd05cf2017-03-15 23:23:36 +0300558 float toa, max = -1.0, avg = 0.0;
Thomas Tsoue90a42b2013-11-13 23:38:09 -0500559 int max_i = -1;
Thomas Tsou30421a72013-11-13 23:14:48 -0500560 signalVector *burst;
Thomas Tsoua0179e32013-11-14 15:52:04 -0500561 TransceiverState *state = &mStates[chan];
dburgessb3a0ca42011-10-12 07:44:40 +0000562
Thomas Tsou30421a72013-11-13 23:14:48 -0500563 /* Blocking FIFO read */
564 radioVector *radio_burst = mReceiveFIFO[chan]->read();
565 if (!radio_burst)
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200566 return false;
dburgessb3a0ca42011-10-12 07:44:40 +0000567
Thomas Tsou30421a72013-11-13 23:14:48 -0500568 /* Set time and determine correlation type */
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200569 bi->burstTime = radio_burst->getTime();
570 CorrType type = expectedCorrType(bi->burstTime, chan);
dburgessb3a0ca42011-10-12 07:44:40 +0000571
Tom Tsou64464e62016-07-01 03:46:46 -0700572 /* Enable 8-PSK burst detection if EDGE is enabled */
573 if (mEdge && (type == TSC))
574 type = EDGE;
575
Alexander Chemerise692ce92015-06-12 00:15:31 -0400576 /* Debug: dump bursts to disk */
577 /* bits 0-7 - chan 0 timeslots
578 * bits 8-15 - chan 1 timeslots */
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200579 if (mWriteBurstToDiskMask & ((1<<bi->burstTime.TN()) << (8*chan)))
Alexander Chemerise692ce92015-06-12 00:15:31 -0400580 writeToFile(radio_burst, chan);
581
Alexander Chemeris2b542102015-06-08 22:46:38 -0400582 /* No processing if the timeslot is off.
583 * Not even power level or noise calculation. */
584 if (type == OFF) {
Thomas Tsou30421a72013-11-13 23:14:48 -0500585 delete radio_burst;
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200586 return false;
dburgessb3a0ca42011-10-12 07:44:40 +0000587 }
kurtis.heimerl3ed6fb72011-11-26 03:17:52 +0000588
Thomas Tsoue90a42b2013-11-13 23:38:09 -0500589 /* Select the diversity channel with highest energy */
590 for (size_t i = 0; i < radio_burst->chans(); i++) {
Alexander Chemeris1dd05cf2017-03-15 23:23:36 +0300591 float pow = energyDetect(*radio_burst->getVector(i), 20 * mSPSRx);
Thomas Tsoue90a42b2013-11-13 23:38:09 -0500592 if (pow > max) {
593 max = pow;
594 max_i = i;
595 }
596 avg += pow;
597 }
598
599 if (max_i < 0) {
600 LOG(ALERT) << "Received empty burst";
601 delete radio_burst;
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200602 return false;
Thomas Tsoue90a42b2013-11-13 23:38:09 -0500603 }
604
Thomas Tsou30421a72013-11-13 23:14:48 -0500605 /* Average noise on diversity paths and update global levels */
Thomas Tsoue90a42b2013-11-13 23:38:09 -0500606 burst = radio_burst->getVector(max_i);
Thomas Tsouef25dba2013-11-14 15:31:24 -0500607 avg = sqrt(avg / radio_burst->chans());
Alexander Chemeris2b542102015-06-08 22:46:38 -0400608
Pau Espin Pedrol607a4142019-07-01 13:56:17 +0200609 bi->rssi = 20.0 * log10(rxFullScale / avg) + rssiOffset;
Alexander Chemeris2b542102015-06-08 22:46:38 -0400610
Alexander Chemeris2b542102015-06-08 22:46:38 -0400611 if (type == IDLE) {
612 /* Update noise levels */
613 state->mNoises.insert(avg);
614 state->mNoiseLev = state->mNoises.avg();
Pau Espin Pedrol607a4142019-07-01 13:56:17 +0200615 bi->noise = 20.0 * log10(rxFullScale / state->mNoiseLev) + rssiOffset;
Alexander Chemeris2b542102015-06-08 22:46:38 -0400616
617 delete radio_burst;
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200618 return false;
Alexander Chemeris2b542102015-06-08 22:46:38 -0400619 } else {
620 /* Do not update noise levels */
Pau Espin Pedrol607a4142019-07-01 13:56:17 +0200621 bi->noise = 20.0 * log10(rxFullScale / state->mNoiseLev) + rssiOffset;
Alexander Chemeris2b542102015-06-08 22:46:38 -0400622 }
Thomas Tsoufa3a7872013-10-17 21:23:34 -0400623
Vadim Yanitskiya8b35652018-10-22 02:52:18 +0200624 unsigned max_toa = (type == RACH || type == EXT_RACH) ?
625 mMaxExpectedDelayAB : mMaxExpectedDelayNB;
626
Thomas Tsou30421a72013-11-13 23:14:48 -0500627 /* Detect normal or RACH bursts */
Vadim Yanitskiya8b35652018-10-22 02:52:18 +0200628 rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, mSPSRx, type, amp, toa, max_toa);
Thomas Tsouf0782732013-10-29 15:55:47 -0400629
Tom Tsoub0aefcb2016-03-06 03:44:34 -0800630 if (rc > 0) {
631 type = (CorrType) rc;
632 } else if (rc <= 0) {
633 if (rc == -SIGERR_CLIP) {
Alexander Chemeris954b1182015-06-04 15:39:41 -0400634 LOG(WARNING) << "Clipping detected on received RACH or Normal Burst";
Tom Tsoub0aefcb2016-03-06 03:44:34 -0800635 } else if (rc != SIGERR_NONE) {
Alexander Chemeris954b1182015-06-04 15:39:41 -0400636 LOG(WARNING) << "Unhandled RACH or Normal Burst detection error";
Tom Tsou577cd022015-05-18 13:57:54 -0700637 }
638
Thomas Tsou30421a72013-11-13 23:14:48 -0500639 delete radio_burst;
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200640 return false;
dburgessb3a0ca42011-10-12 07:44:40 +0000641 }
dburgessb3a0ca42011-10-12 07:44:40 +0000642
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200643 bi->toa = toa;
644 bi->rxBurst = demodAnyBurst(*burst, mSPSRx, amp, toa, type);
Thomas Tsouef25dba2013-11-14 15:31:24 -0500645
Pau Espin Pedrol0e67cf22019-07-02 14:59:47 +0200646 /* EDGE demodulator returns 444 (gSlotLen * 3) bits */
647 if (bi->rxBurst->size() == EDGE_BURST_NBITS)
648 bi->nbits = EDGE_BURST_NBITS;
649 else /* size() here is actually gSlotLen + 8, due to guard periods */
650 bi->nbits = gSlotLen;
651
Pau Espin Pedrol25ae1902019-07-01 16:40:44 +0200652 // Convert -1..+1 soft bits to 0..1 soft bits
653 vectorSlicer(bi->rxBurst);
654
Thomas Tsou30421a72013-11-13 23:14:48 -0500655 delete radio_burst;
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200656 return true;
dburgessb3a0ca42011-10-12 07:44:40 +0000657}
658
dburgessb3a0ca42011-10-12 07:44:40 +0000659void Transceiver::reset()
660{
Thomas Tsou204a9f12013-10-29 18:34:16 -0400661 for (size_t i = 0; i < mTxPriorityQueues.size(); i++)
662 mTxPriorityQueues[i].clear();
dburgessb3a0ca42011-10-12 07:44:40 +0000663}
664
Pau Espin Pedrol7c405a02017-07-04 16:24:06 +0200665
Vadim Yanitskiybd0efb02018-03-09 02:45:07 +0700666#define MAX_PACKET_LENGTH 100
667
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700668/**
669 * Matches a buffer with a command.
670 * @param buf a buffer to look command in
671 * @param cmd a command to look in buffer
672 * @param params pointer to arguments, or NULL
673 * @return true if command matches, otherwise false
674 */
675static bool match_cmd(char *buf,
676 const char *cmd, char **params)
677{
678 size_t cmd_len = strlen(cmd);
679
680 /* Check a command itself */
681 if (strncmp(buf, cmd, cmd_len))
682 return false;
683
684 /* A command has arguments */
685 if (params != NULL) {
686 /* Make sure there is a space */
687 if (buf[cmd_len] != ' ')
688 return false;
689
690 /* Update external pointer */
691 *params = buf + cmd_len + 1;
692 }
693
694 return true;
695}
696
Thomas Tsou204a9f12013-10-29 18:34:16 -0400697void Transceiver::driveControl(size_t chan)
dburgessb3a0ca42011-10-12 07:44:40 +0000698{
Vadim Yanitskiy4d9b59c2018-03-09 02:54:45 +0700699 char buffer[MAX_PACKET_LENGTH + 1];
700 char response[MAX_PACKET_LENGTH + 1];
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700701 char *command, *params;
Vadim Yanitskiy4d9b59c2018-03-09 02:54:45 +0700702 int msgLen;
Thomas Tsoud647ec52013-10-29 15:17:34 -0400703
Vadim Yanitskiy4d9b59c2018-03-09 02:54:45 +0700704 /* Attempt to read from control socket */
705 msgLen = mCtrlSockets[chan]->read(buffer, MAX_PACKET_LENGTH);
706 if (msgLen < 1)
dburgessb3a0ca42011-10-12 07:44:40 +0000707 return;
Vadim Yanitskiy4d9b59c2018-03-09 02:54:45 +0700708
709 /* Zero-terminate received string */
710 buffer[msgLen] = '\0';
dburgessb3a0ca42011-10-12 07:44:40 +0000711
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700712 /* Verify a command signature */
713 if (strncmp(buffer, "CMD ", 4)) {
Pau Espin Pedrol441d82a2018-12-04 16:37:24 +0100714 LOGC(DTRXCTRL, WARNING) << "bogus message on control interface";
dburgessb3a0ca42011-10-12 07:44:40 +0000715 return;
716 }
dburgessb3a0ca42011-10-12 07:44:40 +0000717
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700718 /* Set command pointer */
719 command = buffer + 4;
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200720 LOGCHAN(chan, DTRXCTRL, INFO) << "command is '" << command << "'";
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700721
722 if (match_cmd(command, "POWEROFF", NULL)) {
Tom Tsoueb54bdd2014-11-25 16:06:32 -0800723 stop();
724 sprintf(response,"RSP POWEROFF 0");
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700725 } else if (match_cmd(command, "POWERON", NULL)) {
Tom Tsou365bc382016-10-19 15:26:04 -0700726 if (!start()) {
dburgessb3a0ca42011-10-12 07:44:40 +0000727 sprintf(response,"RSP POWERON 1");
Tom Tsou365bc382016-10-19 15:26:04 -0700728 } else {
dburgessb3a0ca42011-10-12 07:44:40 +0000729 sprintf(response,"RSP POWERON 0");
Alexander Chemeris5a068062015-06-20 01:38:47 +0300730 for (int i = 0; i < 8; i++) {
731 for (int j = 0; j < 8; j++)
732 mHandover[i][j] = false;
733 }
Tom Tsou365bc382016-10-19 15:26:04 -0700734 }
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700735 } else if (match_cmd(command, "HANDOVER", &params)) {
Vadim Yanitskiyc0c6d702018-03-09 05:08:23 +0700736 unsigned ts = 0, ss = 0;
737 sscanf(params, "%u %u", &ts, &ss);
738 if (ts > 7 || ss > 7) {
739 sprintf(response, "RSP NOHANDOVER 1 %u %u", ts, ss);
740 } else {
741 mHandover[ts][ss] = true;
742 sprintf(response, "RSP HANDOVER 0 %u %u", ts, ss);
743 }
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700744 } else if (match_cmd(command, "NOHANDOVER", &params)) {
Vadim Yanitskiyc0c6d702018-03-09 05:08:23 +0700745 unsigned ts = 0, ss = 0;
746 sscanf(params, "%u %u", &ts, &ss);
747 if (ts > 7 || ss > 7) {
748 sprintf(response, "RSP NOHANDOVER 1 %u %u", ts, ss);
749 } else {
750 mHandover[ts][ss] = false;
751 sprintf(response, "RSP NOHANDOVER 0 %u %u", ts, ss);
752 }
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700753 } else if (match_cmd(command, "SETMAXDLY", &params)) {
dburgessb3a0ca42011-10-12 07:44:40 +0000754 //set expected maximum time-of-arrival
755 int maxDelay;
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700756 sscanf(params, "%d", &maxDelay);
Alexander Chemeris78d1fc92016-03-19 21:16:22 +0300757 mMaxExpectedDelayAB = maxDelay; // 1 GSM symbol is approx. 1 km
dburgessb3a0ca42011-10-12 07:44:40 +0000758 sprintf(response,"RSP SETMAXDLY 0 %d",maxDelay);
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700759 } else if (match_cmd(command, "SETMAXDLYNB", &params)) {
Alexander Chemeris78d1fc92016-03-19 21:16:22 +0300760 //set expected maximum time-of-arrival
761 int maxDelay;
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700762 sscanf(params, "%d", &maxDelay);
Alexander Chemeris78d1fc92016-03-19 21:16:22 +0300763 mMaxExpectedDelayNB = maxDelay; // 1 GSM symbol is approx. 1 km
764 sprintf(response,"RSP SETMAXDLYNB 0 %d",maxDelay);
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700765 } else if (match_cmd(command, "SETRXGAIN", &params)) {
dburgessb3a0ca42011-10-12 07:44:40 +0000766 //set expected maximum time-of-arrival
767 int newGain;
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700768 sscanf(params, "%d", &newGain);
Thomas Tsou204a9f12013-10-29 18:34:16 -0400769 newGain = mRadioInterface->setRxGain(newGain, chan);
dburgessb3a0ca42011-10-12 07:44:40 +0000770 sprintf(response,"RSP SETRXGAIN 0 %d",newGain);
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700771 } else if (match_cmd(command, "NOISELEV", NULL)) {
dburgessb3a0ca42011-10-12 07:44:40 +0000772 if (mOn) {
Thomas Tsoua0179e32013-11-14 15:52:04 -0500773 float lev = mStates[chan].mNoiseLev;
dburgessb3a0ca42011-10-12 07:44:40 +0000774 sprintf(response,"RSP NOISELEV 0 %d",
Thomas Tsoua0179e32013-11-14 15:52:04 -0500775 (int) round(20.0 * log10(rxFullScale / lev)));
dburgessb3a0ca42011-10-12 07:44:40 +0000776 }
777 else {
778 sprintf(response,"RSP NOISELEV 1 0");
779 }
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700780 } else if (match_cmd(command, "SETPOWER", &params)) {
Tom Tsoua4d1a412014-11-25 15:46:56 -0800781 int power;
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700782 sscanf(params, "%d", &power);
Tom Tsoua4d1a412014-11-25 15:46:56 -0800783 power = mRadioInterface->setPowerAttenuation(power, chan);
784 mStates[chan].mPower = power;
785 sprintf(response, "RSP SETPOWER 0 %d", power);
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700786 } else if (match_cmd(command, "ADJPOWER", &params)) {
Tom Tsoua4d1a412014-11-25 15:46:56 -0800787 int power, step;
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700788 sscanf(params, "%d", &step);
Tom Tsoua4d1a412014-11-25 15:46:56 -0800789 power = mStates[chan].mPower + step;
790 power = mRadioInterface->setPowerAttenuation(power, chan);
791 mStates[chan].mPower = power;
792 sprintf(response, "RSP ADJPOWER 0 %d", power);
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700793 } else if (match_cmd(command, "RXTUNE", &params)) {
dburgessb3a0ca42011-10-12 07:44:40 +0000794 // tune receiver
795 int freqKhz;
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700796 sscanf(params, "%d", &freqKhz);
Thomas Tsou477b77c2013-11-15 16:13:59 -0500797 mRxFreq = freqKhz * 1e3;
Thomas Tsou204a9f12013-10-29 18:34:16 -0400798 if (!mRadioInterface->tuneRx(mRxFreq, chan)) {
Pau Espin Pedrol441d82a2018-12-04 16:37:24 +0100799 LOGC(DTRXCTRL, ALERT) << "RX failed to tune";
dburgessb3a0ca42011-10-12 07:44:40 +0000800 sprintf(response,"RSP RXTUNE 1 %d",freqKhz);
801 }
802 else
803 sprintf(response,"RSP RXTUNE 0 %d",freqKhz);
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700804 } else if (match_cmd(command, "TXTUNE", &params)) {
dburgessb3a0ca42011-10-12 07:44:40 +0000805 // tune txmtr
806 int freqKhz;
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700807 sscanf(params, "%d", &freqKhz);
Thomas Tsou477b77c2013-11-15 16:13:59 -0500808 mTxFreq = freqKhz * 1e3;
Thomas Tsou204a9f12013-10-29 18:34:16 -0400809 if (!mRadioInterface->tuneTx(mTxFreq, chan)) {
Pau Espin Pedrol441d82a2018-12-04 16:37:24 +0100810 LOGC(DTRXCTRL, ALERT) << "TX failed to tune";
dburgessb3a0ca42011-10-12 07:44:40 +0000811 sprintf(response,"RSP TXTUNE 1 %d",freqKhz);
812 }
813 else
814 sprintf(response,"RSP TXTUNE 0 %d",freqKhz);
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700815 } else if (match_cmd(command, "SETTSC", &params)) {
dburgessb3a0ca42011-10-12 07:44:40 +0000816 // set TSC
Thomas Tsou3f32ab52013-11-15 16:32:54 -0500817 unsigned TSC;
Vadim Yanitskiy8c6c5d22018-03-09 05:01:21 +0700818 sscanf(params, "%u", &TSC);
Tom Tsou60317342017-03-30 19:36:41 -0700819 if (TSC > 7) {
Thomas Tsoud3fccea2013-11-15 14:22:53 -0500820 sprintf(response, "RSP SETTSC 1 %d", TSC);
Tom Tsou60317342017-03-30 19:36:41 -0700821 } else {
Pau Espin Pedrol441d82a2018-12-04 16:37:24 +0100822 LOGC(DTRXCTRL, NOTICE) << "Changing TSC from " << mTSC << " to " << TSC;
dburgessb3a0ca42011-10-12 07:44:40 +0000823 mTSC = TSC;
Thomas Tsou83e06892013-08-20 16:10:01 -0400824 sprintf(response,"RSP SETTSC 0 %d", TSC);
dburgessb3a0ca42011-10-12 07:44:40 +0000825 }
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700826 } else if (match_cmd(command, "SETSLOT", &params)) {
Alexander Chemeris1fe52822015-05-20 12:02:29 -0700827 // set slot type
dburgessb3a0ca42011-10-12 07:44:40 +0000828 int corrCode;
829 int timeslot;
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700830 sscanf(params, "%d %d", &timeslot, &corrCode);
dburgessb3a0ca42011-10-12 07:44:40 +0000831 if ((timeslot < 0) || (timeslot > 7)) {
Pau Espin Pedrol441d82a2018-12-04 16:37:24 +0100832 LOGC(DTRXCTRL, WARNING) << "bogus message on control interface";
dburgessb3a0ca42011-10-12 07:44:40 +0000833 sprintf(response,"RSP SETSLOT 1 %d %d",timeslot,corrCode);
834 return;
Pau Espin Pedrol7c405a02017-07-04 16:24:06 +0200835 }
Thomas Tsou204a9f12013-10-29 18:34:16 -0400836 mStates[chan].chanType[timeslot] = (ChannelCombination) corrCode;
837 setModulus(timeslot, chan);
dburgessb3a0ca42011-10-12 07:44:40 +0000838 sprintf(response,"RSP SETSLOT 0 %d %d",timeslot,corrCode);
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700839 } else if (match_cmd(command, "_SETBURSTTODISKMASK", &params)) {
Alexander Chemerise692ce92015-06-12 00:15:31 -0400840 // debug command! may change or disapear without notice
841 // set a mask which bursts to dump to disk
842 int mask;
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700843 sscanf(params, "%d", &mask);
Alexander Chemerise692ce92015-06-12 00:15:31 -0400844 mWriteBurstToDiskMask = mask;
845 sprintf(response,"RSP _SETBURSTTODISKMASK 0 %d",mask);
Vadim Yanitskiya62fcf72018-03-09 03:24:08 +0700846 } else {
Pau Espin Pedrol441d82a2018-12-04 16:37:24 +0100847 LOGC(DTRXCTRL, WARNING) << "bogus command " << command << " on control interface.";
Alexander Chemeris4438a9f2013-04-08 00:14:08 +0200848 sprintf(response,"RSP ERR 1");
dburgessb3a0ca42011-10-12 07:44:40 +0000849 }
850
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200851 LOGCHAN(chan, DTRXCTRL, INFO) << "response is '" << response << "'";
Thomas Tsou204a9f12013-10-29 18:34:16 -0400852 mCtrlSockets[chan]->write(response, strlen(response) + 1);
dburgessb3a0ca42011-10-12 07:44:40 +0000853}
854
Thomas Tsou204a9f12013-10-29 18:34:16 -0400855bool Transceiver::driveTxPriorityQueue(size_t chan)
dburgessb3a0ca42011-10-12 07:44:40 +0000856{
Tom Tsoue8871082016-07-01 02:46:04 -0700857 int burstLen;
858 char buffer[EDGE_BURST_NBITS + 50];
dburgessb3a0ca42011-10-12 07:44:40 +0000859
860 // check data socket
Tom Tsou2c650a62016-04-28 21:55:17 -0700861 size_t msgLen = mDataSockets[chan]->read(buffer, sizeof(buffer));
dburgessb3a0ca42011-10-12 07:44:40 +0000862
Tom Tsoue8871082016-07-01 02:46:04 -0700863 if (msgLen == gSlotLen + 1 + 4 + 1) {
864 burstLen = gSlotLen;
865 } else if (msgLen == EDGE_BURST_NBITS + 1 + 4 + 1) {
866 if (mSPSTx != 4)
867 return false;
868
869 burstLen = EDGE_BURST_NBITS;
870 } else {
dburgessb3a0ca42011-10-12 07:44:40 +0000871 LOG(ERR) << "badly formatted packet on GSM->TRX interface";
872 return false;
873 }
874
875 int timeSlot = (int) buffer[0];
876 uint64_t frameNum = 0;
877 for (int i = 0; i < 4; i++)
878 frameNum = (frameNum << 8) | (0x0ff & buffer[i+1]);
dburgessb3a0ca42011-10-12 07:44:40 +0000879
dburgessb3a0ca42011-10-12 07:44:40 +0000880 LOG(DEBUG) << "rcvd. burst at: " << GSM::Time(frameNum,timeSlot);
Pau Espin Pedrol7c405a02017-07-04 16:24:06 +0200881
dburgessb3a0ca42011-10-12 07:44:40 +0000882 int RSSI = (int) buffer[5];
Tom Tsou7c741ec2016-07-19 11:20:59 -0700883 BitVector newBurst(burstLen);
dburgessb3a0ca42011-10-12 07:44:40 +0000884 BitVector::iterator itr = newBurst.begin();
885 char *bufferItr = buffer+6;
Pau Espin Pedrol7c405a02017-07-04 16:24:06 +0200886 while (itr < newBurst.end())
dburgessb3a0ca42011-10-12 07:44:40 +0000887 *itr++ = *bufferItr++;
Pau Espin Pedrol7c405a02017-07-04 16:24:06 +0200888
dburgessb3a0ca42011-10-12 07:44:40 +0000889 GSM::Time currTime = GSM::Time(frameNum,timeSlot);
Thomas Tsou204a9f12013-10-29 18:34:16 -0400890
891 addRadioVector(chan, newBurst, RSSI, currTime);
dburgessb3a0ca42011-10-12 07:44:40 +0000892
893 return true;
894
895
896}
dburgessb3a0ca42011-10-12 07:44:40 +0000897
Thomas Tsou204a9f12013-10-29 18:34:16 -0400898void Transceiver::driveReceiveRadio()
899{
Pau Espin Pedroldb936b92018-09-03 16:50:49 +0200900 int rc = mRadioInterface->driveReceiveRadio();
901 if (rc == 0) {
Thomas Tsou204a9f12013-10-29 18:34:16 -0400902 usleep(100000);
Pau Espin Pedroldb936b92018-09-03 16:50:49 +0200903 } else if (rc < 0) {
904 LOG(FATAL) << "radio Interface receive failed, requesting stop.";
Pau Espin Pedrolb426e4a2019-06-04 12:39:28 +0200905 osmo_signal_dispatch(SS_MAIN, S_MAIN_STOP_REQUIRED, NULL);
Pau Espin Pedrol934da482017-07-04 16:25:20 +0200906 } else if (mForceClockInterface || mTransmitDeadlineClock > mLastClockUpdateTime + GSM::Time(216,0)) {
907 mForceClockInterface = false;
908 writeClockInterface();
Alexander Chemeris6a2bf0d2015-05-24 19:28:09 -0400909 }
Thomas Tsou204a9f12013-10-29 18:34:16 -0400910}
911
Pau Espin Pedrol607a4142019-07-01 13:56:17 +0200912void Transceiver::logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi)
Tom Tsoub0aefcb2016-03-06 03:44:34 -0800913{
914 LOG(DEBUG) << std::fixed << std::right
Alexander Chemeris58e95912016-03-25 18:20:28 +0300915 << " chan: " << chan
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200916 << " time: " << bi->burstTime
Pau Espin Pedrol607a4142019-07-01 13:56:17 +0200917 << " RSSI: " << std::setw(5) << std::setprecision(1) << (bi->rssi - rssiOffset)
918 << "dBFS/" << std::setw(6) << -bi->rssi << "dBm"
919 << " noise: " << std::setw(5) << std::setprecision(1) << (bi->noise - rssiOffset)
920 << "dBFS/" << std::setw(6) << -bi->noise << "dBm"
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200921 << " TOA: " << std::setw(5) << std::setprecision(2) << bi->toa
922 << " bits: " << *(bi->rxBurst);
Tom Tsoub0aefcb2016-03-06 03:44:34 -0800923}
924
Thomas Tsou204a9f12013-10-29 18:34:16 -0400925void Transceiver::driveReceiveFIFO(size_t chan)
926{
Alexander Chemerise8905a02015-06-03 23:47:56 -0400927 int TOAint; // in 1/256 symbols
dburgessb3a0ca42011-10-12 07:44:40 +0000928
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200929 struct trx_ul_burst_ind bi;
930
931 if (!pullRadioVector(chan, &bi))
932 return;
dburgessb3a0ca42011-10-12 07:44:40 +0000933
Pau Espin Pedrol607a4142019-07-01 13:56:17 +0200934 logRxBurst(chan, &bi);
Alexander Chemerisdbe26ab2015-06-04 00:14:51 -0400935
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200936 TOAint = (int) (bi.toa * 256.0 + 0.5); // round to closest integer
dburgessb3a0ca42011-10-12 07:44:40 +0000937
Pau Espin Pedrol0e67cf22019-07-02 14:59:47 +0200938 char burstString[sizeof(struct trxd_hdr_v0) + bi.nbits + 2];
Pau Espin Pedrol778b30a2019-06-28 13:27:24 +0200939 struct trxd_hdr_v0* pkt = (struct trxd_hdr_v0*)burstString;
940 pkt->common.version = 0;
941 pkt->common.reserved = 0;
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200942 pkt->common.tn = bi.burstTime.TN();
943 osmo_store32be(bi.burstTime.FN(), &pkt->common.fn);
Pau Espin Pedrol607a4142019-07-01 13:56:17 +0200944 pkt->v0.rssi = bi.rssi;
Pau Espin Pedrol778b30a2019-06-28 13:27:24 +0200945 osmo_store16be(TOAint, &pkt->v0.toa);
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200946 SoftVector::iterator burstItr = bi.rxBurst->begin();
dburgessb3a0ca42011-10-12 07:44:40 +0000947
Pau Espin Pedrol0e67cf22019-07-02 14:59:47 +0200948 for (unsigned i = 0; i < bi.nbits; i++)
Pau Espin Pedrol778b30a2019-06-28 13:27:24 +0200949 pkt->soft_bits[i] = (char) round((*burstItr++) * 255.0);
Tom Tsoub0aefcb2016-03-06 03:44:34 -0800950
Pau Espin Pedrol778b30a2019-06-28 13:27:24 +0200951 /* +1: Historical reason. There's an uninitizalied byte in there: pkt->soft_bits[bi.nbits] */
Pau Espin Pedrol0e67cf22019-07-02 14:59:47 +0200952 pkt->soft_bits[bi.nbits + 1] = '\0';
Pau Espin Pedrolddd18a52019-06-28 17:01:16 +0200953 delete bi.rxBurst;
Tom Tsoub0aefcb2016-03-06 03:44:34 -0800954
Pau Espin Pedrol0e67cf22019-07-02 14:59:47 +0200955 mDataSockets[chan]->write(burstString, sizeof(struct trxd_hdr_v0) + bi.nbits + 2);
dburgessb3a0ca42011-10-12 07:44:40 +0000956}
957
Thomas Tsou204a9f12013-10-29 18:34:16 -0400958void Transceiver::driveTxFIFO()
dburgessb3a0ca42011-10-12 07:44:40 +0000959{
960
961 /**
Pau Espin Pedrol7c405a02017-07-04 16:24:06 +0200962 Features a carefully controlled latency mechanism, to
dburgessb3a0ca42011-10-12 07:44:40 +0000963 assure that transmit packets arrive at the radio/USRP
964 before they need to be transmitted.
965
966 Deadline clock indicates the burst that needs to be
967 pushed into the FIFO right NOW. If transmit queue does
968 not have a burst, stick in filler data.
969 */
970
971
972 RadioClock *radioClock = (mRadioInterface->getClock());
Pau Espin Pedrol7c405a02017-07-04 16:24:06 +0200973
dburgessb3a0ca42011-10-12 07:44:40 +0000974 if (mOn) {
975 //radioClock->wait(); // wait until clock updates
976 LOG(DEBUG) << "radio clock " << radioClock->get();
977 while (radioClock->get() + mTransmitLatency > mTransmitDeadlineClock) {
978 // if underrun, then we're not providing bursts to radio/USRP fast
979 // enough. Need to increase latency by one GSM frame.
Thomas Tsou02d88d12013-04-05 15:36:30 -0400980 if (mRadioInterface->getWindowType() == RadioDevice::TX_WINDOW_USRP1) {
kurtis.heimerle380af32011-11-26 03:18:55 +0000981 if (mRadioInterface->isUnderrun()) {
ttsou2173abf2012-08-08 00:51:31 +0000982 // only update latency at the defined frame interval
983 if (radioClock->get() > mLatencyUpdateTime + GSM::Time(USB_LATENCY_INTRVL)) {
kurtis.heimerle380af32011-11-26 03:18:55 +0000984 mTransmitLatency = mTransmitLatency + GSM::Time(1,0);
Pau Espin Pedrol74bcc562018-10-02 17:30:28 +0200985 LOG(INFO) << "new latency: " << mTransmitLatency << " (underrun "
986 << radioClock->get() << " vs " << mLatencyUpdateTime + GSM::Time(USB_LATENCY_INTRVL) << ")";
kurtis.heimerle380af32011-11-26 03:18:55 +0000987 mLatencyUpdateTime = radioClock->get();
988 }
989 }
990 else {
991 // if underrun hasn't occurred in the last sec (216 frames) drop
992 // transmit latency by a timeslot
Pau Espin Pedrole564f0f2018-04-24 18:43:51 +0200993 if (mTransmitLatency > mRadioInterface->minLatency()) {
kurtis.heimerle380af32011-11-26 03:18:55 +0000994 if (radioClock->get() > mLatencyUpdateTime + GSM::Time(216,0)) {
995 mTransmitLatency.decTN();
996 LOG(INFO) << "reduced latency: " << mTransmitLatency;
997 mLatencyUpdateTime = radioClock->get();
998 }
999 }
1000 }
dburgessb3a0ca42011-10-12 07:44:40 +00001001 }
dburgessb3a0ca42011-10-12 07:44:40 +00001002 // time to push burst to transmit FIFO
1003 pushRadioVector(mTransmitDeadlineClock);
1004 mTransmitDeadlineClock.incTN();
1005 }
dburgessb3a0ca42011-10-12 07:44:40 +00001006 }
Thomas Tsou92c16df2013-09-28 18:04:19 -04001007
1008 radioClock->wait();
dburgessb3a0ca42011-10-12 07:44:40 +00001009}
1010
1011
1012
1013void Transceiver::writeClockInterface()
1014{
1015 char command[50];
1016 // FIXME -- This should be adaptive.
1017 sprintf(command,"IND CLOCK %llu",(unsigned long long) (mTransmitDeadlineClock.FN()+2));
1018
1019 LOG(INFO) << "ClockInterface: sending " << command;
1020
Tom Tsoueb54bdd2014-11-25 16:06:32 -08001021 mClockSocket.write(command, strlen(command) + 1);
dburgessb3a0ca42011-10-12 07:44:40 +00001022
1023 mLastClockUpdateTime = mTransmitDeadlineClock;
1024
Thomas Tsou92c16df2013-09-28 18:04:19 -04001025}
dburgessb3a0ca42011-10-12 07:44:40 +00001026
Thomas Tsou204a9f12013-10-29 18:34:16 -04001027void *RxUpperLoopAdapter(TransceiverChannel *chan)
1028{
Pau Espin Pedrol5b60c982018-09-20 18:04:46 +02001029 char thread_name[16];
Thomas Tsou204a9f12013-10-29 18:34:16 -04001030 Transceiver *trx = chan->trx;
1031 size_t num = chan->num;
1032
1033 delete chan;
1034
Pau Espin Pedrol5b60c982018-09-20 18:04:46 +02001035 snprintf(thread_name, 16, "RxUpper%zu", num);
1036 set_selfthread_name(thread_name);
1037
Thomas Tsou7553aa92013-11-08 12:50:03 -05001038 trx->setPriority(0.42);
1039
Thomas Tsou204a9f12013-10-29 18:34:16 -04001040 while (1) {
1041 trx->driveReceiveFIFO(num);
1042 pthread_testcancel();
1043 }
1044 return NULL;
1045}
1046
1047void *RxLowerLoopAdapter(Transceiver *transceiver)
dburgessb3a0ca42011-10-12 07:44:40 +00001048{
Pau Espin Pedrol5b60c982018-09-20 18:04:46 +02001049 set_selfthread_name("RxLower");
1050
Thomas Tsou7553aa92013-11-08 12:50:03 -05001051 transceiver->setPriority(0.45);
kurtis.heimerl6b495a52011-11-26 03:17:21 +00001052
dburgessb3a0ca42011-10-12 07:44:40 +00001053 while (1) {
Thomas Tsou204a9f12013-10-29 18:34:16 -04001054 transceiver->driveReceiveRadio();
Thomas Tsou92c16df2013-09-28 18:04:19 -04001055 pthread_testcancel();
1056 }
1057 return NULL;
1058}
1059
Thomas Tsou204a9f12013-10-29 18:34:16 -04001060void *TxLowerLoopAdapter(Transceiver *transceiver)
Thomas Tsou92c16df2013-09-28 18:04:19 -04001061{
Pau Espin Pedrol5b60c982018-09-20 18:04:46 +02001062 set_selfthread_name("TxLower");
1063
Thomas Tsou7553aa92013-11-08 12:50:03 -05001064 transceiver->setPriority(0.44);
1065
Thomas Tsou92c16df2013-09-28 18:04:19 -04001066 while (1) {
Thomas Tsou204a9f12013-10-29 18:34:16 -04001067 transceiver->driveTxFIFO();
dburgessb3a0ca42011-10-12 07:44:40 +00001068 pthread_testcancel();
1069 }
1070 return NULL;
1071}
1072
Thomas Tsou204a9f12013-10-29 18:34:16 -04001073void *ControlServiceLoopAdapter(TransceiverChannel *chan)
dburgessb3a0ca42011-10-12 07:44:40 +00001074{
Pau Espin Pedrol5b60c982018-09-20 18:04:46 +02001075 char thread_name[16];
Thomas Tsou204a9f12013-10-29 18:34:16 -04001076 Transceiver *trx = chan->trx;
1077 size_t num = chan->num;
1078
1079 delete chan;
1080
Pau Espin Pedrol5b60c982018-09-20 18:04:46 +02001081 snprintf(thread_name, 16, "CtrlService%zu", num);
1082 set_selfthread_name(thread_name);
1083
dburgessb3a0ca42011-10-12 07:44:40 +00001084 while (1) {
Thomas Tsou204a9f12013-10-29 18:34:16 -04001085 trx->driveControl(num);
dburgessb3a0ca42011-10-12 07:44:40 +00001086 pthread_testcancel();
1087 }
1088 return NULL;
1089}
1090
Thomas Tsou204a9f12013-10-29 18:34:16 -04001091void *TxUpperLoopAdapter(TransceiverChannel *chan)
dburgessb3a0ca42011-10-12 07:44:40 +00001092{
Pau Espin Pedrol5b60c982018-09-20 18:04:46 +02001093 char thread_name[16];
Thomas Tsou204a9f12013-10-29 18:34:16 -04001094 Transceiver *trx = chan->trx;
1095 size_t num = chan->num;
1096
1097 delete chan;
1098
Pau Espin Pedrol5b60c982018-09-20 18:04:46 +02001099 snprintf(thread_name, 16, "TxUpper%zu", num);
1100 set_selfthread_name(thread_name);
1101
Thomas Tsoua4cf48c2013-11-09 21:44:26 -05001102 trx->setPriority(0.40);
1103
dburgessb3a0ca42011-10-12 07:44:40 +00001104 while (1) {
Tom Tsoueb54bdd2014-11-25 16:06:32 -08001105 trx->driveTxPriorityQueue(num);
dburgessb3a0ca42011-10-12 07:44:40 +00001106 pthread_testcancel();
1107 }
1108 return NULL;
1109}