blob: ed51cdb687452dde8c4aaf29828eb8893c6c6543 [file] [log] [blame]
Harald Welte940738e2018-03-07 07:50:57 +01001/*
2* Copyright 2018 sysmocom - s.f.m.c. GmbH
3*
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Affero General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Affero General Public License for more details.
13
14 You should have received a copy of the GNU Affero General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#include <stdint.h>
19#include <string.h>
20#include <stdlib.h>
21#include "Logger.h"
22#include "Threads.h"
23#include "LMSDevice.h"
24
25#include <lime/LimeSuite.h>
26
27#ifdef HAVE_CONFIG_H
28#include "config.h"
29#endif
30
31using namespace std;
32
33const double LMSDevice::masterClockRate = 52.0e6;
34
35LMSDevice::LMSDevice(size_t sps)
36{
37 LOG(INFO) << "creating LMS device...";
38
39 m_lms_device = NULL;
40 this->sps = sps;
41}
42
43static void lms_log_callback(int lvl, const char *msg)
44{
45 /* map lime specific log levels */
46 static const lvl_map[4] = {
47 [0] = LOGL_FATAL,
48 [1] = LOGL_ERROR,
49 [2] = LOGL_NOTICE,
50 [3] = LOGL_INFO,
51 [4] = LOGL_DEBUG,
52 };
53 /* protect against future higher log level values (lower importance) */
54 if (lvl >= ARRAY_SIZE(lvl_map))
55 lvl = ARRAY_SIZE(lvl_map)-1;
56
57 LOG(lvl) << msg;
58}
59
60int LMSDevice::open(const std::string &, int, bool)
61{
62 lms_info_str dev_str;
63 uint16_t dac_val;
64
65 LOG(INFO) << "opening LMS device..";
66
67 LMS_RegisterLogHandler(&lms_log_callback);
68
69 rc = LMS_Open(&m_lms_dev, NULL, NULL);
70 if (rc != 0)
71 return -1;
72
73 if (LMS_SetSampleRate(m_lms_dev, GSMRATE, sps) < 0)
74 goto out_close;
75 /* FIXME: make this device/model dependent, like UHDDevice:dev_param_map! */
76 ts_offset = static_caset<TIMESTAMP>(8.9e-5 * GSMRATE);
77
78 switch (ref) {
79 case REF_INTERNAL:
80 /* Ugly API: Selecting clock source implicit by writing to VCTCXO DAC ?!? */
81 if (LMS_VCTCXORead(m_lms_dev, &dac_val) < 0)
82 goto out_close;
83
84 if (LMS_VCTCXOWrite(m_lms_dev, dac_val) < 0)
85 goto out_close;
86 break;
87 case REF_EXTENAL:
88 /* Assume an external 10 MHz reference clock */
89 if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, 10000000.0) < 0)
90 goto out_close;
91 break;
92 default:
93 LOG(ALERT) << "Invalid reference type";
94 goto out_close;
95 }
96
97 if (LMS_Init(m_lms_dev) < 0)
98 goto out_close;
99
100 /* Perform Rx and Tx calibration */
101 if (LMS_Calibrate(m_lms_dev, LMS_CH_RX, chan, 270000.0, 0) < 0)
102 goto out_close;
103 if (LMS_Calibrate(m_lms_dev, LMS_CH_TX, chan, 270000.0, 0) < 0)
104 goto out_close;
105
106 samplesRead = 0;
107 samplesWritten = 0;
108 started = false;
109
110 return NORMAL;
111
112out_close:
113 LOG(ALERT) << "Error in LMS open, closing: " << LMS_GetLastErrorMessage();
114 LMS_Close(m_lms_dev);
115 return -1;
116}
117
118bool LMSDevice::start()
119{
120 LOG(INFO) << "starting LMS...";
121
122 if (LMS_EnableChannel(m_lms_dev, LMS_CH_RX, 0, true) < 0)
123 return false;
124
125 if (LMS_EnableChannel(m_lms_dev, LMS_CH_TX, 0, true) < 0)
126 return false;
127
128 // Set gains to midpoint
129 setTxGain((minTxGain() + maxTxGain()) / 2);
130 setRxGain((minRxGain() + maxRxGain()) / 2);
131
132 m_lms_stream_rx = {
133 .isTx = false,
134 .channel = 0,
135 .fifoSize = 1024 * 1024,
136 .throughputVsLatency = 0.3,
137 .dataFmt = LMS_FMT_I16,
138 }
139 m_lms_stream_tx = {
140 .ixTx = true,
141 .channel = 0,
142 .fifoSize = 1024 * 1024,
143 .throughputVsLatency = 0.3,
144 .dataFmt = LMS_FMT_I16,
145 }
146
147 if (LMS_SetupStream(m_lms_dev, &m_lms_stream_rx) < 0)
148 return false;
149
150 if (LMS_SetupStream(m_lms_dev, &m_lms_stream_tx) < 0)
151 return false;
152
153 if (LMS_StartStream(&m_lms_stream_rx) < 0)
154 return false;
155
156 if (LMS_StartStream(&m_lms_stream_tx) < 0)
157 return false;
158
159 started = true;
160 return true;
161}
162
163bool LMSDevice::stop()
164{
165 if (!started)
166 return true;
167
168 LMS_StopStream(&m_lms_stream_tx);
169 LMS_StopStream(&m_lms_stream_rx);
170
171 LMS_EnableChannel(m_lms_dev, LMS_CH_RX, 0, false);
172 LMS_EnableChannel(m_lms_dev, LMS_CH_TX, 0, false);
173
174 return true;
175}
176
177double LMSDevice::maxTxGain()
178{
179 return 60.0;
180}
181
182double LMSDevice::minTxGain()
183{
184 return 0.0;
185}
186
187double LMSDevice::maxRxGain()
188{
189 return 70.0;
190}
191
192double LMSDevice::minRxGain()
193{
194 return 0.0;
195}
196
197double LMSDevice::setTxGain(double dB, size_t chan)
198{
199 if (chan) {
200 LOG(ALERT) << "Invalid channel " << chan;
201 return 0.0;
202 }
203
204 if (dB > maxTxGain())
205 dB = maxTxGain();
206 if (dB < minTxGain())
207 dB = minTxGain();
208
209 LOG(NOTICE) << "Setting TX gain to " << dB << " dB.";
210
211 if (LMS_SetGaindB(m_lms_dev, LMS_CH_TX, chan, dB) < 0)
212 LOG(ERR) << "Error setting TX gain";
213
214 return dB;
215}
216
217double LMSDevice::setRxGain(double dB, size_t chan)
218{
219 if (chan) {
220 LOG(ALERT) << "Invalid channel " << chan;
221 return 0.0;
222 }
223
224 dB = 47.0;
225
226 if (dB > maxRxGain())
227 dB = maxRxGain();
228 if (dB < minRxGain())
229 dB = minRxGain();
230
231 LOG(NOTICE) << "Setting RX gain to " << dB << " dB.";
232
233 if (LMS_SetGaindB(m_lms_dev, LMS_CH_RX, chan, dB) < 0)
234 LOG(ERR) << "Error setting RX gain";
235
236 return dB;
237}
238
239int get_ant_idx(const char *name, bool dir_tx)
240{
241 lms_name_t name_list;
242 int num_names;
243 num_names = LMS_GetAntennaList(m_lms_dev, dir_tx, &name_list);
244 for (i = 0; i < num_names; i++) {
245 if (!strcmp(name, name_list[i]))
246 return i;
247 }
248 return -1;
249}
250
251bool LMSDevice::setRxAntenna(const std::string & ant, size_t chan)
252{
253 int idx;
254
255 if (chan >= rx_paths.size()) {
256 LOG(ALERT) << "Requested non-existent channel " << chan;
257 return false;
258 }
259
260 idx = get_ant_idx(ant, LMS_CH_RX);
261 if (idx < 0) {
262 LOG(ALERT) << "Invalid Rx Antenna";
263 return false;
264 }
265
266 if (LMS_SetAntenna(m_lms_dev, LMS_CH_RX, chan, idx) < 0) {
267 LOG(ALERT) << "Unable to set Rx Antenna";
268 }
269
270 return true;
271}
272
273std::string LMSDevice::getRxAntenna(size_t chan)
274{
275 if (chan >= rx_paths.size()) {
276 LOG(ALERT) << "Requested non-existent channel " << chan;
277 return "";
278 }
279
280 idx = LMS_GetAntenna(m_lms_dev, LMS_CH_RX, chan);
281 if (idx < 0) {
282 LOG(ALERT) << "Error getting Rx Antenna";
283 return "";
284 }
285
286 if (LMS_GetAntennaList(m_lms_dev, LMS_CH_RX, chan, &list) < idx) {
287 LOG(ALERT) << "Error getting Rx Antenna List";
288 return "";
289 }
290
291 return list[idx];
292}
293
294bool LMSDevice::setTxAntenna(const std::string & ant, size_t chan)
295{
296 int idx;
297
298 if (chan >= tx_paths.size()) {
299 LOG(ALERT) << "Requested non-existent channel " << chan;
300 return false;
301 }
302
303 idx = get_ant_idx(ant, LMS_CH_TX);
304 if (idx < 0) {
305 LOG(ALERT) << "Invalid Rx Antenna";
306 return false;
307 }
308
309 if (LMS_SetAntenna(m_lms_dev, LMS_CH_TX, chan, idx) < 0) {
310 LOG(ALERT) << "Unable to set Rx Antenna";
311 }
312
313 return true;
314}
315
316std::string LMSDevice::getTxAntenna(size_t chan)
317{
318 int idx;
319
320 if (chan >= tx_paths.size()) {
321 LOG(ALERT) << "Requested non-existent channel " << chan;
322 return "";
323 }
324
325 idx = LMS_GetAntenna(m_lms_dev, LMS_CH_TX, chan);
326 if (idx < 0) {
327 LOG(ALERT) << "Error getting Tx Antenna";
328 return "";
329 }
330
331 if (LMS_GetAntennaList(m_lms_dev, LMS_CH_TX, chan, &list) < idx) {
332 LOG(ALERT) << "Error getting Tx Antenna List";
333 return "";
334 }
335
336 return list[idx];
337}
338
339// NOTE: Assumes sequential reads
340int LMSDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun,
341 TIMESTAMP timestamp, bool * underrun, unsigned *RSSI)
342{
343 lms_stream_meta_t rx_metadata = {
344 .flushPartialPacket = false,
345 .waitForTimestamp = false,
346 };
347 int rc;
348
349 if (bufs.size != 1) {
350 LOG(ALERT) << "Invalid channel combination " << bufs.size();
351 return -1;
352 }
353
354 /* Shift read time with respect to transmit clock */
355 timestamp += ts_offset;
356
357 rc = LMS_RecvStream(&m_lms_stream_rx, bufs[0], len, &rx_metadata, 100);
358
359 *overrun = false;
360 *underrun = false;
361
362 if (LMS_GetStreamStatus(&m_lms_stream_rx, &status) == 0) {
363 if (status.underrun > m_last_rx_underruns)
364 *underrun = true;
365 m_last_rx_underruns = status.underrun;
366
367 if (status.overrun > m_last_rx_overruns)
368 *overrun = true;
369 m_last_rx_overruns = status.overrun;
370 }
371
372 samplesRead += rc;
373
374 return rc;
375}
376
377int LMSDevice::writeSamples(std::vector < short *>&bufs, int len,
378 bool * underrun, unsigned long long timestamp,
379 bool isControl)
380{
381 lms_stream_status_t status;
382 lms_stream_meta_t tx_metadata = {
383 .flushPartialPacket = false,
384 .waitForTimestamp = true,
385 .timestamp = timestamp,
386 };
387 int rc;
388
389 if (isControl) {
390 LOG(ERR) << "Control packets not supported";
391 return 0;
392 }
393
394 if (bufs.size() != 1) {
395 LOG(ALERT) << "Invalid channel combination " << bufs.size();
396 return -1;
397 }
398
399 rc = LMS_Send_Stream(&m_lms_stream_tx, bufs[0], len, &tx_metadata, 100);
400 if (rc != len) {
401 LOG(ALERT) << "LMS: Device send timed out ";
402 }
403
404 *underrun = false;
405
406 if (LMS_GetStreamStatus(&m_lms_stream_tx, &status) == 0) {
407 if (status.underrun > m_last_tx_underruns)
408 *underrun = true;
409 m_last_tx_underruns = status.underrun;
410 }
411
412 samplesWritten += rc;
413
414 return rc;
415}
416
417bool LMSDevice::updateAlignment(TIMESTAMP timestamp)
418{
419 short data[] = { 0x00, 0x02, 0x00, 0x00 };
420 uint32_t *wordPtr = (uint32_t *) data;
421 *wordPtr = host_to_usrp_u32(*wordPtr);
422 bool tmpUnderrun;
423
424 std::vector < short *>buf(1, data);
425 if (writeSamples(buf, 1, &tmpUnderrun, timestamp & 0x0ffffffffll, true)) {
426 pingTimestamp = timestamp;
427 return true;
428 }
429 return false;
430}
431
432bool LMSDevice::setTxFreq(double wFreq, size_t chan)
433{
434
435 if (chan) {
436 LOG(ALERT) << "Invalid channel " << chan;
437 return false;
438 }
439
440 if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) {
441 LOG(ALERT) << "set Tx: " << wFreq << " failed!";
442 return false;
443 }
444
445 return true;
446}
447
448bool LMSDevice::setRxFreq(double wFreq, size_t chan)
449{
450 if (chan) {
451 LOG(ALERT) << "Invalid channel " << chan;
452 return false;
453 }
454
455 if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) {
456 LOG(ALERT) << "set Rx: " << wFreq << " failed!";
457 return false;
458 }
459
460 return true;
461}
462
463RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
464 InterfaceType iface, size_t chans, double offset,
465 const std::vector < std::string > &tx_paths,
466 const std::vector < std::string > &rx_paths)
467{
468 return new LMSDevice(tx_sps);
469}