blob: e546fd619e403377439b8a5b12367d7ee04499c3 [file] [log] [blame]
Harald Welte940738e2018-03-07 07:50:57 +01001/*
2* Copyright 2018 sysmocom - s.f.m.c. GmbH
3*
Pau Espin Pedrol21d03d32019-07-22 12:05:52 +02004* SPDX-License-Identifier: AGPL-3.0+
5*
Harald Welte940738e2018-03-07 07:50:57 +01006 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU Affero General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU Affero General Public License for more details.
15
16 You should have received a copy of the GNU Affero General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#include <stdint.h>
21#include <string.h>
22#include <stdlib.h>
23#include "Logger.h"
24#include "Threads.h"
25#include "LMSDevice.h"
Oliver Smith871713b2018-12-10 17:10:36 +010026#include "Utils.h"
Harald Welte940738e2018-03-07 07:50:57 +010027
28#include <lime/LimeSuite.h>
29
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +020030extern "C" {
31#include "osmo_signal.h"
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020032#include <osmocom/core/utils.h>
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +020033}
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020034
Harald Welte940738e2018-03-07 07:50:57 +010035#ifdef HAVE_CONFIG_H
36#include "config.h"
37#endif
38
39using namespace std;
40
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020041#define MAX_ANTENNA_LIST_SIZE 10
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020042#define GSM_CARRIER_BW 270000.0 /* 270kHz */
43#define LMS_MIN_BW_SUPPORTED 2.5e6 /* 2.5mHz, minimum supported by LMS */
44#define LMS_CALIBRATE_BW_HZ OSMO_MAX(GSM_CARRIER_BW, LMS_MIN_BW_SUPPORTED)
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +020045#define SAMPLE_BUF_SZ (1 << 20) /* Size of Rx timestamp based Ring buffer, in bytes */
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020046
Pau Espin Pedrolb0e54262020-01-13 16:00:04 +010047LMSDevice::LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
Harald Welte105a61e2018-06-13 21:55:09 +020048 const std::vector<std::string>& tx_paths,
49 const std::vector<std::string>& rx_paths):
Pau Espin Pedrolb0e54262020-01-13 16:00:04 +010050 RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths),
Pau Espin Pedrolbf583702020-01-07 16:10:34 +010051 m_lms_dev(NULL), started(false)
Harald Welte940738e2018-03-07 07:50:57 +010052{
Harald Welte5cc88582018-08-17 19:55:38 +020053 LOGC(DDEV, INFO) << "creating LMS device...";
Harald Welte940738e2018-03-07 07:50:57 +010054
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020055 m_lms_stream_rx.resize(chans);
56 m_lms_stream_tx.resize(chans);
Pau Espin Pedrol705a3482019-09-13 16:51:48 +020057 rx_gains.resize(chans);
58 tx_gains.resize(chans);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020059
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +020060 rx_buffers.resize(chans);
Harald Welte940738e2018-03-07 07:50:57 +010061}
62
Pau Espin Pedrol5cea18e2018-12-03 18:17:18 +010063LMSDevice::~LMSDevice()
64{
Joachim Steiger2d130fb2019-04-16 16:22:23 +020065 unsigned int i;
Pau Espin Pedrol5cea18e2018-12-03 18:17:18 +010066 LOGC(DDEV, INFO) << "Closing LMS device";
67 if (m_lms_dev) {
Joachim Steiger2d130fb2019-04-16 16:22:23 +020068 /* disable all channels */
69 for (i=0; i<chans; i++) {
70 LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, false);
71 LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, false);
72 }
Pau Espin Pedrol5cea18e2018-12-03 18:17:18 +010073 LMS_Close(m_lms_dev);
74 m_lms_dev = NULL;
75 }
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +020076
77 for (size_t i = 0; i < rx_buffers.size(); i++)
78 delete rx_buffers[i];
Pau Espin Pedrol5cea18e2018-12-03 18:17:18 +010079}
80
Harald Welte940738e2018-03-07 07:50:57 +010081static void lms_log_callback(int lvl, const char *msg)
82{
83 /* map lime specific log levels */
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020084 static const int lvl_map[5] = {
Harald Welte940738e2018-03-07 07:50:57 +010085 [0] = LOGL_FATAL,
Pau Espin Pedrol32b3c2e2018-11-23 14:39:06 +010086 [LMS_LOG_ERROR] = LOGL_ERROR,
87 [LMS_LOG_WARNING] = LOGL_NOTICE,
88 [LMS_LOG_INFO] = LOGL_INFO,
89 [LMS_LOG_DEBUG] = LOGL_DEBUG,
Harald Welte940738e2018-03-07 07:50:57 +010090 };
91 /* protect against future higher log level values (lower importance) */
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020092 if ((unsigned int) lvl >= ARRAY_SIZE(lvl_map))
Harald Welte940738e2018-03-07 07:50:57 +010093 lvl = ARRAY_SIZE(lvl_map)-1;
94
Pau Espin Pedrolaebbfe02020-01-02 16:39:11 +010095 LOGLV(DDEVDRV, lvl_map[lvl]) << msg;
Harald Welte940738e2018-03-07 07:50:57 +010096}
97
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +020098static void print_range(const char* name, lms_range_t *range)
99{
Harald Welte5cc88582018-08-17 19:55:38 +0200100 LOGC(DDEV, INFO) << name << ": Min=" << range->min << " Max=" << range->max
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +0200101 << " Step=" << range->step;
102}
103
Oliver Smith871713b2018-12-10 17:10:36 +0100104/*! Find the device string that matches all filters from \a args.
105 * \param[in] info_list device addresses found by LMS_GetDeviceList()
106 * \param[in] count length of info_list
107 * \param[in] args dev-args value from osmo-trx.cfg, containing comma separated key=value pairs
108 * \return index of first matching device or -1 (no match) */
109int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::string &args)
110{
111 unsigned int i, j;
112 vector<string> filters;
113
114 filters = comma_delimited_to_vector(args.c_str());
115
116 /* iterate over device addresses */
117 for (i=0; i < count; i++) {
118 /* check if all filters match */
119 bool match = true;
120 for (j=0; j < filters.size(); j++) {
121 if (!strstr(info_list[i], filters[j].c_str())) {
122 match = false;
123 break;
124 }
125 }
126
127 if (match)
128 return i;
129 }
130 return -1;
131}
132
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200133int LMSDevice::open(const std::string &args, int ref, bool swap_channels)
134{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200135 lms_info_str_t* info_list;
Joachim Steiger28752902019-04-16 17:10:13 +0200136 const lms_dev_info_t* device_info;
Joachim Steiger4ce45552019-04-16 16:35:53 +0200137 lms_range_t range_sr;
138 float_type sr_host, sr_rf;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200139 unsigned int i, n;
Oliver Smith871713b2018-12-10 17:10:36 +0100140 int rc, dev_id;
Pau Espin Pedrol223a15c2020-01-13 17:53:44 +0100141 int sample_rate;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200142
Harald Welte5cc88582018-08-17 19:55:38 +0200143 LOGC(DDEV, INFO) << "Opening LMS device..";
Harald Welte940738e2018-03-07 07:50:57 +0100144
145 LMS_RegisterLogHandler(&lms_log_callback);
146
Harald Welte62b79002018-06-13 23:32:42 +0200147 if ((n = LMS_GetDeviceList(NULL)) < 0)
Harald Welte5cc88582018-08-17 19:55:38 +0200148 LOGC(DDEV, ERROR) << "LMS_GetDeviceList(NULL) failed";
149 LOGC(DDEV, INFO) << "Devices found: " << n;
Harald Welte62b79002018-06-13 23:32:42 +0200150 if (n < 1)
151 return -1;
Harald Welte940738e2018-03-07 07:50:57 +0100152
Harald Welte62b79002018-06-13 23:32:42 +0200153 info_list = new lms_info_str_t[n];
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200154
Harald Welte62b79002018-06-13 23:32:42 +0200155 if (LMS_GetDeviceList(info_list) < 0)
Harald Welte5cc88582018-08-17 19:55:38 +0200156 LOGC(DDEV, ERROR) << "LMS_GetDeviceList(info_list) failed";
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200157
Harald Welte62b79002018-06-13 23:32:42 +0200158 for (i = 0; i < n; i++)
Harald Welte5cc88582018-08-17 19:55:38 +0200159 LOGC(DDEV, INFO) << "Device [" << i << "]: " << info_list[i];
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200160
Oliver Smith871713b2018-12-10 17:10:36 +0100161 dev_id = info_list_find(info_list, n, args);
162 if (dev_id == -1) {
163 LOGC(DDEV, ERROR) << "No LMS device found with address '" << args << "'";
164 delete[] info_list;
165 return -1;
166 }
167
168 LOGC(DDEV, INFO) << "Using device[" << dev_id << "]";
169 rc = LMS_Open(&m_lms_dev, info_list[dev_id], NULL);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200170 if (rc != 0) {
Harald Welte5cc88582018-08-17 19:55:38 +0200171 LOGC(DDEV, ERROR) << "LMS_GetDeviceList() failed)";
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200172 delete [] info_list;
173 return -1;
174 }
175
176 delete [] info_list;
177
Joachim Steiger28752902019-04-16 17:10:13 +0200178 device_info = LMS_GetDeviceInfo(m_lms_dev);
179
180 if ((ref != REF_EXTERNAL) && (ref != REF_INTERNAL)){
181 LOGC(DDEV, ERROR) << "Invalid reference type";
182 goto out_close;
183 }
184
185 /* if reference clock is external setup must happen _before_ calling LMS_Init */
186 /* FIXME make external reference frequency configurable */
187 if (ref == REF_EXTERNAL) {
188 LOGC(DDEV, INFO) << "Setting External clock reference to 10MHz";
189 /* Assume an external 10 MHz reference clock */
190 if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, 10000000.0) < 0)
191 goto out_close;
192 }
193
Harald Welte5cc88582018-08-17 19:55:38 +0200194 LOGC(DDEV, INFO) << "Init LMS device";
Harald Welte9cb4f272018-04-28 21:38:58 +0200195 if (LMS_Init(m_lms_dev) != 0) {
Harald Welte5cc88582018-08-17 19:55:38 +0200196 LOGC(DDEV, ERROR) << "LMS_Init() failed";
Pau Espin Pedroled361f92018-12-04 12:42:30 +0100197 goto out_close;
Harald Welte9cb4f272018-04-28 21:38:58 +0200198 }
199
Joachim Steiger28752902019-04-16 17:10:13 +0200200 /* LimeSDR-Mini does not have switches but needs soldering to select external/internal clock */
201 /* LimeNET-Micro also does not like selecting internal clock*/
202 /* also set device specific maximum tx levels selected by phasenoise measurements*/
203 if (strncmp(device_info->deviceName,"LimeSDR-USB",11) == 0){
204 /* if reference clock is internal setup must happen _after_ calling LMS_Init */
205 /* according to lms using LMS_CLOCK_EXTREF with a frequency <= 0 is the correct way to set clock to internal reference*/
206 if (ref == REF_INTERNAL) {
207 LOGC(DDEV, INFO) << "Setting Internal clock reference";
208 if (LMS_SetClockFreq(m_lms_dev, LMS_CLOCK_EXTREF, -1) < 0)
209 goto out_close;
210 }
211 maxTxGainClamp = 73.0;
212 } else if (strncmp(device_info->deviceName,"LimeSDR-Mini",12) == 0)
213 maxTxGainClamp = 66.0;
214 else
215 maxTxGainClamp = 71.0; /* "LimeNET-Micro", etc FIXME pciE based LMS boards?*/
216
Joachim Steiger2d130fb2019-04-16 16:22:23 +0200217 /* enable all used channels */
218 for (i=0; i<chans; i++) {
219 if (LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, true) < 0)
220 goto out_close;
221 if (LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, true) < 0)
222 goto out_close;
223 }
224
Joachim Steigerc785fb12019-04-16 19:04:37 +0200225 /* set samplerate */
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +0200226 if (LMS_GetSampleRateRange(m_lms_dev, LMS_CH_RX, &range_sr))
Harald Weltea4381142018-04-28 21:41:07 +0200227 goto out_close;
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +0200228 print_range("Sample Rate", &range_sr);
Harald Weltea4381142018-04-28 21:41:07 +0200229
Pau Espin Pedrol223a15c2020-01-13 17:53:44 +0100230 sample_rate = (iface == MULTI_ARFCN ? MCBTS_SPACING : GSMRATE) * tx_sps;
231
232 LOGC(DDEV, INFO) << "Setting sample rate to " << sample_rate << " " << tx_sps;
233 if (LMS_SetSampleRate(m_lms_dev, sample_rate, 32) < 0)
Harald Welte940738e2018-03-07 07:50:57 +0100234 goto out_close;
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +0200235
Harald Weltea4381142018-04-28 21:41:07 +0200236 if (LMS_GetSampleRate(m_lms_dev, LMS_CH_RX, 0, &sr_host, &sr_rf))
237 goto out_close;
Harald Welte5cc88582018-08-17 19:55:38 +0200238 LOGC(DDEV, INFO) << "Sample Rate: Host=" << sr_host << " RF=" << sr_rf;
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +0200239
Harald Welte940738e2018-03-07 07:50:57 +0100240 /* FIXME: make this device/model dependent, like UHDDevice:dev_param_map! */
Pau Espin Pedrol223a15c2020-01-13 17:53:44 +0100241 ts_offset = static_cast<TIMESTAMP>(8.9e-5 * sample_rate);
Harald Welte940738e2018-03-07 07:50:57 +0100242
Joachim Steigerc785fb12019-04-16 19:04:37 +0200243 /* configure antennas */
Harald Weltecfb9dac2018-06-13 21:56:24 +0200244 if (!set_antennas()) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200245 LOGC(DDEV, FATAL) << "LMS antenna setting failed";
Joachim Steigerc785fb12019-04-16 19:04:37 +0200246 goto out_close;
Harald Weltecfb9dac2018-06-13 21:56:24 +0200247 }
248
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200249 /* Set up per-channel Rx timestamp based Ring buffers */
250 for (size_t i = 0; i < rx_buffers.size(); i++)
251 rx_buffers[i] = new smpl_buf(SAMPLE_BUF_SZ / sizeof(uint32_t));
252
Pau Espin Pedrol223a15c2020-01-13 17:53:44 +0100253 return iface == MULTI_ARFCN ? MULTI_ARFCN : NORMAL;
Harald Welte940738e2018-03-07 07:50:57 +0100254
255out_close:
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200256 LOGC(DDEV, FATAL) << "Error in LMS open, closing: " << LMS_GetLastErrorMessage();
Harald Welte940738e2018-03-07 07:50:57 +0100257 LMS_Close(m_lms_dev);
Pau Espin Pedrol5cea18e2018-12-03 18:17:18 +0100258 m_lms_dev = NULL;
Harald Welte940738e2018-03-07 07:50:57 +0100259 return -1;
260}
261
262bool LMSDevice::start()
263{
Harald Welte5cc88582018-08-17 19:55:38 +0200264 LOGC(DDEV, INFO) << "starting LMS...";
Harald Welte940738e2018-03-07 07:50:57 +0100265
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200266 unsigned int i;
Harald Welte940738e2018-03-07 07:50:57 +0100267
Pau Espin Pedrol69869bd2018-12-03 11:19:52 +0100268 if (started) {
269 LOGC(DDEV, ERR) << "Device already started";
270 return false;
271 }
272
Zydrunas Tamosevicius0494d052018-06-12 00:29:16 +0200273 /* configure the channels/streams */
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200274 for (i=0; i<chans; i++) {
Joachim Steiger28752902019-04-16 17:10:13 +0200275 /* Set gains for calibration/filter setup */
276 /* TX gain to maximum */
277 setTxGain(maxTxGain(), i);
278 /* RX gain to midpoint */
Harald Welte55928f22018-11-26 19:26:52 +0100279 setRxGain((minRxGain() + maxRxGain()) / 2, i);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200280
Joachim Steiger4ce45552019-04-16 16:35:53 +0200281 /* set up Rx and Tx filters */
282 if (!do_filters(i))
283 return false;
284 /* Perform Rx and Tx calibration */
285 if (!do_calib(i))
286 return false;
287
Joachim Steigerc785fb12019-04-16 19:04:37 +0200288 /* configure Streams */
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200289 m_lms_stream_rx[i] = {};
290 m_lms_stream_rx[i].isTx = false;
291 m_lms_stream_rx[i].channel = i;
292 m_lms_stream_rx[i].fifoSize = 1024 * 1024;
293 m_lms_stream_rx[i].throughputVsLatency = 0.3;
294 m_lms_stream_rx[i].dataFmt = lms_stream_t::LMS_FMT_I16;
295
296 m_lms_stream_tx[i] = {};
297 m_lms_stream_tx[i].isTx = true;
298 m_lms_stream_tx[i].channel = i;
299 m_lms_stream_tx[i].fifoSize = 1024 * 1024;
300 m_lms_stream_tx[i].throughputVsLatency = 0.3;
301 m_lms_stream_tx[i].dataFmt = lms_stream_t::LMS_FMT_I16;
302
303 if (LMS_SetupStream(m_lms_dev, &m_lms_stream_rx[i]) < 0)
304 return false;
305
306 if (LMS_SetupStream(m_lms_dev, &m_lms_stream_tx[i]) < 0)
307 return false;
Zydrunas Tamosevicius0494d052018-06-12 00:29:16 +0200308 }
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200309
Zydrunas Tamosevicius0494d052018-06-12 00:29:16 +0200310 /* now start the streams in a second loop, as we can no longer call
311 * LMS_SetupStream() after LMS_StartStream() of the first stream */
312 for (i = 0; i < chans; i++) {
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200313 if (LMS_StartStream(&m_lms_stream_rx[i]) < 0)
314 return false;
315
316 if (LMS_StartStream(&m_lms_stream_tx[i]) < 0)
317 return false;
Harald Welte940738e2018-03-07 07:50:57 +0100318 }
319
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200320 flush_recv(10);
Harald Welte940738e2018-03-07 07:50:57 +0100321
322 started = true;
323 return true;
324}
325
326bool LMSDevice::stop()
327{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200328 unsigned int i;
329
Harald Welte940738e2018-03-07 07:50:57 +0100330 if (!started)
331 return true;
332
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200333 for (i=0; i<chans; i++) {
334 LMS_StopStream(&m_lms_stream_tx[i]);
335 LMS_StopStream(&m_lms_stream_rx[i]);
Pau Espin Pedrolb4ea7b52018-12-03 11:34:23 +0100336 }
Harald Welte940738e2018-03-07 07:50:57 +0100337
Pau Espin Pedrolb4ea7b52018-12-03 11:34:23 +0100338 for (i=0; i<chans; i++) {
339 LMS_DestroyStream(m_lms_dev, &m_lms_stream_tx[i]);
340 LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200341 }
Harald Welte940738e2018-03-07 07:50:57 +0100342
Pau Espin Pedrol69869bd2018-12-03 11:19:52 +0100343 started = false;
Harald Welte940738e2018-03-07 07:50:57 +0100344 return true;
345}
346
Joachim Steiger4ce45552019-04-16 16:35:53 +0200347/* do rx/tx calibration - depends on gain, freq and bw */
348bool LMSDevice::do_calib(size_t chan)
349{
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200350 LOGCHAN(chan, DDEV, INFO) << "Calibrating";
Joachim Steiger4ce45552019-04-16 16:35:53 +0200351 if (LMS_Calibrate(m_lms_dev, LMS_CH_RX, chan, LMS_CALIBRATE_BW_HZ, 0) < 0)
352 return false;
353 if (LMS_Calibrate(m_lms_dev, LMS_CH_TX, chan, LMS_CALIBRATE_BW_HZ, 0) < 0)
354 return false;
355 return true;
356}
357
358/* do rx/tx filter config - depends on bw only? */
359bool LMSDevice::do_filters(size_t chan)
360{
361 lms_range_t range_lpfbw_rx, range_lpfbw_tx;
362 float_type lpfbw_rx, lpfbw_tx;
363
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200364 LOGCHAN(chan, DDEV, INFO) << "Setting filters";
Joachim Steiger4ce45552019-04-16 16:35:53 +0200365 if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_rx))
366 return false;
367 print_range("LPFBWRange Rx", &range_lpfbw_rx);
368 if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_tx))
369 return false;
370 print_range("LPFBWRange Tx", &range_lpfbw_tx);
371
372 lpfbw_rx = OSMO_MIN(OSMO_MAX(1.4001e6, range_lpfbw_rx.min), range_lpfbw_rx.max);
373 lpfbw_tx = OSMO_MIN(OSMO_MAX(5.2e6, range_lpfbw_tx.min), range_lpfbw_tx.max);
374
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200375 LOGCHAN(chan, DDEV, INFO) << "LPFBW: Rx=" << lpfbw_rx << " Tx=" << lpfbw_tx;
Joachim Steiger4ce45552019-04-16 16:35:53 +0200376
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200377 LOGCHAN(chan, DDEV, INFO) << "Setting LPFBW";
Joachim Steiger4ce45552019-04-16 16:35:53 +0200378 if (LMS_SetLPFBW(m_lms_dev, LMS_CH_RX, chan, lpfbw_rx) < 0)
379 return false;
380 if (LMS_SetLPFBW(m_lms_dev, LMS_CH_TX, chan, lpfbw_tx) < 0)
381 return false;
382 return true;
383}
384
385
Harald Welte940738e2018-03-07 07:50:57 +0100386double LMSDevice::maxTxGain()
387{
Joachim Steiger28752902019-04-16 17:10:13 +0200388 return maxTxGainClamp;
Harald Welte940738e2018-03-07 07:50:57 +0100389}
390
391double LMSDevice::minTxGain()
392{
393 return 0.0;
394}
395
396double LMSDevice::maxRxGain()
397{
Pau Espin Pedrol587916e2018-05-08 18:46:28 +0200398 return 73.0;
Harald Welte940738e2018-03-07 07:50:57 +0100399}
400
401double LMSDevice::minRxGain()
402{
403 return 0.0;
404}
405
406double LMSDevice::setTxGain(double dB, size_t chan)
407{
Harald Welte940738e2018-03-07 07:50:57 +0100408 if (dB > maxTxGain())
409 dB = maxTxGain();
410 if (dB < minTxGain())
411 dB = minTxGain();
412
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200413 LOGCHAN(chan, DDEV, NOTICE) << "Setting TX gain to " << dB << " dB";
Harald Welte940738e2018-03-07 07:50:57 +0100414
415 if (LMS_SetGaindB(m_lms_dev, LMS_CH_TX, chan, dB) < 0)
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200416 LOGCHAN(chan, DDEV, ERR) << "Error setting TX gain to " << dB << " dB";
Pau Espin Pedrol705a3482019-09-13 16:51:48 +0200417 else
418 tx_gains[chan] = dB;
419 return tx_gains[chan];
Harald Welte940738e2018-03-07 07:50:57 +0100420}
421
422double LMSDevice::setRxGain(double dB, size_t chan)
423{
Harald Welte940738e2018-03-07 07:50:57 +0100424 if (dB > maxRxGain())
425 dB = maxRxGain();
426 if (dB < minRxGain())
427 dB = minRxGain();
428
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200429 LOGCHAN(chan, DDEV, NOTICE) << "Setting RX gain to " << dB << " dB";
Harald Welte940738e2018-03-07 07:50:57 +0100430
431 if (LMS_SetGaindB(m_lms_dev, LMS_CH_RX, chan, dB) < 0)
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200432 LOGCHAN(chan, DDEV, ERR) << "Error setting RX gain to " << dB << " dB";
Pau Espin Pedrol705a3482019-09-13 16:51:48 +0200433 else
434 rx_gains[chan] = dB;
435 return rx_gains[chan];
Harald Welte940738e2018-03-07 07:50:57 +0100436}
437
Pau Espin Pedrole0010fa2019-08-26 17:10:27 +0200438void LMSDevice::log_ant_list(bool dir_tx, size_t chan, std::ostringstream& os)
439{
440 lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
441 int num_names;
442 int i;
443
444 num_names = LMS_GetAntennaList(m_lms_dev, dir_tx, chan, name_list);
445 for (i = 0; i < num_names; i++) {
446 if (i)
447 os << ", ";
448 os << "'" << name_list[i] << "'";
449 }
450}
451
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200452int LMSDevice::get_ant_idx(const std::string & name, bool dir_tx, size_t chan)
Harald Welte940738e2018-03-07 07:50:57 +0100453{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200454 lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
455 const char* c_name = name.c_str();
Harald Welte940738e2018-03-07 07:50:57 +0100456 int num_names;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200457 int i;
458
459 num_names = LMS_GetAntennaList(m_lms_dev, dir_tx, chan, name_list);
Harald Welte940738e2018-03-07 07:50:57 +0100460 for (i = 0; i < num_names; i++) {
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200461 if (!strcmp(c_name, name_list[i]))
Harald Welte940738e2018-03-07 07:50:57 +0100462 return i;
463 }
464 return -1;
465}
466
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200467bool LMSDevice::flush_recv(size_t num_pkts)
468{
469 #define CHUNK 625
Harald Weltece70ba52018-06-13 22:47:48 +0200470 int len = CHUNK * tx_sps;
Pau Espin Pedrol541496b2019-04-25 19:22:07 +0200471 short *buffer = (short*) alloca(sizeof(short) * len * 2);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200472 int rc;
473 lms_stream_meta_t rx_metadata = {};
474 rx_metadata.flushPartialPacket = false;
475 rx_metadata.waitForTimestamp = false;
476
477 ts_initial = 0;
478
479 while (!ts_initial || (num_pkts-- > 0)) {
480 rc = LMS_RecvStream(&m_lms_stream_rx[0], &buffer[0], len, &rx_metadata, 100);
Harald Welte5cc88582018-08-17 19:55:38 +0200481 LOGC(DDEV, DEBUG) << "Flush: Recv buffer of len " << rc << " at " << std::hex << rx_metadata.timestamp;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200482 if (rc != len) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200483 LOGC(DDEV, ERROR) << "Flush: Device receive timed out";
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200484 return false;
485 }
486
Harald Welte68f05412018-04-28 21:58:03 +0200487 ts_initial = rx_metadata.timestamp + len;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200488 }
489
Harald Welte5cc88582018-08-17 19:55:38 +0200490 LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200491 return true;
492}
493
Harald Welte940738e2018-03-07 07:50:57 +0100494bool LMSDevice::setRxAntenna(const std::string & ant, size_t chan)
495{
496 int idx;
497
498 if (chan >= rx_paths.size()) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200499 LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
Harald Welte940738e2018-03-07 07:50:57 +0100500 return false;
501 }
502
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200503 idx = get_ant_idx(ant, LMS_CH_RX, chan);
Harald Welte940738e2018-03-07 07:50:57 +0100504 if (idx < 0) {
Pau Espin Pedrole0010fa2019-08-26 17:10:27 +0200505 std::ostringstream os;
506 LOGCHAN(chan, DDEV, ERROR) << "Invalid Rx Antenna: " << ant;
507 log_ant_list(LMS_CH_RX, chan, os);
508 LOGCHAN(chan, DDEV, NOTICE) << "Available Rx Antennas: " << os;
Harald Welte940738e2018-03-07 07:50:57 +0100509 return false;
510 }
511
512 if (LMS_SetAntenna(m_lms_dev, LMS_CH_RX, chan, idx) < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200513 LOGCHAN(chan, DDEV, ERROR) << "Unable to set Rx Antenna";
Harald Welte940738e2018-03-07 07:50:57 +0100514 }
515
516 return true;
517}
518
519std::string LMSDevice::getRxAntenna(size_t chan)
520{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200521 lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
522 int idx;
523
Harald Welte940738e2018-03-07 07:50:57 +0100524 if (chan >= rx_paths.size()) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200525 LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
Harald Welte940738e2018-03-07 07:50:57 +0100526 return "";
527 }
528
529 idx = LMS_GetAntenna(m_lms_dev, LMS_CH_RX, chan);
530 if (idx < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200531 LOGCHAN(chan, DDEV, ERROR) << "Error getting Rx Antenna";
Harald Welte940738e2018-03-07 07:50:57 +0100532 return "";
533 }
534
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200535 if (LMS_GetAntennaList(m_lms_dev, LMS_CH_RX, chan, name_list) < idx) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200536 LOGCHAN(chan, DDEV, ERROR) << "Error getting Rx Antenna List";
Harald Welte940738e2018-03-07 07:50:57 +0100537 return "";
538 }
539
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200540 return name_list[idx];
Harald Welte940738e2018-03-07 07:50:57 +0100541}
542
543bool LMSDevice::setTxAntenna(const std::string & ant, size_t chan)
544{
545 int idx;
546
547 if (chan >= tx_paths.size()) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200548 LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
Harald Welte940738e2018-03-07 07:50:57 +0100549 return false;
550 }
551
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200552 idx = get_ant_idx(ant, LMS_CH_TX, chan);
Harald Welte940738e2018-03-07 07:50:57 +0100553 if (idx < 0) {
Pau Espin Pedrole0010fa2019-08-26 17:10:27 +0200554 std::ostringstream os;
555 LOGCHAN(chan, DDEV, ERROR) << "Invalid Tx Antenna: " << ant;
556 log_ant_list(LMS_CH_TX, chan, os);
557 LOGCHAN(chan, DDEV, NOTICE) << "Available Tx Antennas: " << os;
Harald Welte940738e2018-03-07 07:50:57 +0100558 return false;
559 }
560
561 if (LMS_SetAntenna(m_lms_dev, LMS_CH_TX, chan, idx) < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200562 LOGCHAN(chan, DDEV, ERROR) << "Unable to set Rx Antenna";
Harald Welte940738e2018-03-07 07:50:57 +0100563 }
564
565 return true;
566}
567
568std::string LMSDevice::getTxAntenna(size_t chan)
569{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200570 lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
Harald Welte940738e2018-03-07 07:50:57 +0100571 int idx;
572
573 if (chan >= tx_paths.size()) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200574 LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
Harald Welte940738e2018-03-07 07:50:57 +0100575 return "";
576 }
577
578 idx = LMS_GetAntenna(m_lms_dev, LMS_CH_TX, chan);
579 if (idx < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200580 LOGCHAN(chan, DDEV, ERROR) << "Error getting Tx Antenna";
Harald Welte940738e2018-03-07 07:50:57 +0100581 return "";
582 }
583
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200584 if (LMS_GetAntennaList(m_lms_dev, LMS_CH_TX, chan, name_list) < idx) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200585 LOGCHAN(chan, DDEV, ERROR) << "Error getting Tx Antenna List";
Harald Welte940738e2018-03-07 07:50:57 +0100586 return "";
587 }
588
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200589 return name_list[idx];
590}
591
592bool LMSDevice::requiresRadioAlign()
593{
594 return false;
595}
596
597GSM::Time LMSDevice::minLatency() {
Joachim Steiger6c5f4ba2019-04-16 16:13:02 +0200598 /* UNUSED on limesdr (only used on usrp1/2) */
599 return GSM::Time(0,0);
Harald Welte940738e2018-03-07 07:50:57 +0100600}
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200601/*!
602 * Issue tracking description of several events: https://github.com/myriadrf/LimeSuite/issues/265
603 */
604void LMSDevice::update_stream_stats_rx(size_t chan, bool *overrun)
Pau Espin Pedrole5b66642018-12-04 20:56:32 +0100605{
606 lms_stream_status_t status;
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200607 bool changed = false;
Pau Espin Pedrole5b66642018-12-04 20:56:32 +0100608
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200609 if (LMS_GetStreamStatus(&m_lms_stream_rx[chan], &status) != 0) {
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200610 LOGCHAN(chan, DDEV, ERROR) << "Rx LMS_GetStreamStatus failed";
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200611 return;
Pau Espin Pedrole5b66642018-12-04 20:56:32 +0100612 }
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200613
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200614 /* FIFO overrun is counted when Rx FIFO is full but new data comes from
615 the board and oldest samples in FIFO are overwritte. Value count
616 since the last call to LMS_GetStreamStatus(stream). */
Pau Espin Pedrolbde55af2019-06-04 16:32:25 +0200617 if (status.overrun) {
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200618 changed = true;
619 *overrun = true;
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200620 LOGCHAN(chan, DDEV, ERROR) << "Rx Overrun! ("
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200621 << m_ctr[chan].rx_overruns << " -> "
622 << status.overrun << ")";
623 }
Pau Espin Pedrolbde55af2019-06-04 16:32:25 +0200624 m_ctr[chan].rx_overruns += status.overrun;
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200625
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200626 /* Dropped packets in Rx are counted when gaps in Rx timestamps are
Martin Hauke066fd042019-10-13 19:08:00 +0200627 detected (likely because buffer overflow in hardware). Value count
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200628 since the last call to LMS_GetStreamStatus(stream). */
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200629 if (status.droppedPackets) {
630 changed = true;
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200631 LOGCHAN(chan, DDEV, ERROR) << "Rx Dropped packets by HW! ("
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200632 << m_ctr[chan].rx_dropped_samples << " -> "
633 << m_ctr[chan].rx_dropped_samples +
634 status.droppedPackets
635 << ")";
636 m_ctr[chan].rx_dropped_events++;
637 }
638 m_ctr[chan].rx_dropped_samples += status.droppedPackets;
639
640 if (changed)
641 osmo_signal_dispatch(SS_DEVICE, S_DEVICE_COUNTER_CHANGE, &m_ctr[chan]);
642
Pau Espin Pedrole5b66642018-12-04 20:56:32 +0100643}
644
Harald Welte940738e2018-03-07 07:50:57 +0100645// NOTE: Assumes sequential reads
646int LMSDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun,
647 TIMESTAMP timestamp, bool * underrun, unsigned *RSSI)
648{
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200649 int rc, num_smpls, expect_smpls;
650 ssize_t avail_smpls;
651 TIMESTAMP expect_timestamp;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200652 unsigned int i;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200653 lms_stream_meta_t rx_metadata = {};
654 rx_metadata.flushPartialPacket = false;
655 rx_metadata.waitForTimestamp = false;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200656 rx_metadata.timestamp = 0;
Harald Welte940738e2018-03-07 07:50:57 +0100657
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200658 if (bufs.size() != chans) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200659 LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size();
Harald Welte940738e2018-03-07 07:50:57 +0100660 return -1;
661 }
662
Harald Welte940738e2018-03-07 07:50:57 +0100663 *overrun = false;
664 *underrun = false;
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200665
666 /* Check that timestamp is valid */
667 rc = rx_buffers[0]->avail_smpls(timestamp);
668 if (rc < 0) {
669 LOGC(DDEV, ERROR) << rx_buffers[0]->str_code(rc);
670 LOGC(DDEV, ERROR) << rx_buffers[0]->str_status(timestamp);
671 return 0;
Harald Welte940738e2018-03-07 07:50:57 +0100672 }
673
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200674 for (i = 0; i<chans; i++) {
675 /* Receive samples from HW until we have enough */
676 while ((avail_smpls = rx_buffers[i]->avail_smpls(timestamp)) < len) {
677 thread_enable_cancel(false);
678 num_smpls = LMS_RecvStream(&m_lms_stream_rx[i], bufs[i], len - avail_smpls, &rx_metadata, 100);
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200679 update_stream_stats_rx(i, overrun);
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200680 thread_enable_cancel(true);
681 if (num_smpls <= 0) {
682 LOGCHAN(i, DDEV, ERROR) << "Device receive timed out (" << rc << " vs exp " << len << ").";
683 return -1;
684 }
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200685
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200686 LOGCHAN(i, DDEV, DEBUG) "Received timestamp = " << (TIMESTAMP)rx_metadata.timestamp << " (" << num_smpls << ")";
687
688 expect_smpls = len - avail_smpls;
689 if (expect_smpls != num_smpls)
690 LOGCHAN(i, DDEV, NOTICE) << "Unexpected recv buffer len: expect "
691 << expect_smpls << " got " << num_smpls
692 << ", diff=" << expect_smpls - num_smpls;
693
694 expect_timestamp = timestamp + avail_smpls;
695 if (expect_timestamp != (TIMESTAMP)rx_metadata.timestamp)
696 LOGCHAN(i, DDEV, ERROR) << "Unexpected recv buffer timestamp: expect "
697 << expect_timestamp << " got " << (TIMESTAMP)rx_metadata.timestamp
698 << ", diff=" << rx_metadata.timestamp - expect_timestamp;
699
700 rc = rx_buffers[i]->write(bufs[i], num_smpls, (TIMESTAMP)rx_metadata.timestamp);
701 if (rc < 0) {
702 LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_code(rc);
703 LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_status(timestamp);
704 if (rc != smpl_buf::ERROR_OVERFLOW)
705 return 0;
706 }
707 }
708 }
709
710 /* We have enough samples */
711 for (size_t i = 0; i < rx_buffers.size(); i++) {
712 rc = rx_buffers[i]->read(bufs[i], len, timestamp);
713 if ((rc < 0) || (rc != len)) {
Pau Espin Pedrol62c92802020-01-13 14:35:06 +0100714 LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_code(rc) << ". "
715 << rx_buffers[i]->str_status(timestamp)
716 << ", (len=" << len << ")";
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200717 return 0;
718 }
719 }
720
721 return len;
Harald Welte940738e2018-03-07 07:50:57 +0100722}
723
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200724void LMSDevice::update_stream_stats_tx(size_t chan, bool *underrun)
725{
726 lms_stream_status_t status;
727 bool changed = false;
728
729 if (LMS_GetStreamStatus(&m_lms_stream_tx[chan], &status) != 0) {
730 LOGCHAN(chan, DDEV, ERROR) << "Tx LMS_GetStreamStatus failed";
731 return;
732 }
733
734 /* FIFO underrun is counted when Tx is running but FIFO is empty for
735 >100 ms (500ms in older versions). Value count since the last call to
736 LMS_GetStreamStatus(stream). */
737 if (status.underrun) {
738 changed = true;
739 *underrun = true;
740 LOGCHAN(chan, DDEV, ERROR) << "Tx Underrun! ("
741 << m_ctr[chan].tx_underruns << " -> "
742 << status.underrun << ")";
743 }
744 m_ctr[chan].tx_underruns += status.underrun;
745
746 /* Dropped packets in Tx are counted only when timestamps are enabled
747 and SDR drops packet because of late timestamp. Value count since the
748 last call to LMS_GetStreamStatus(stream). */
749 if (status.droppedPackets) {
750 changed = true;
751 LOGCHAN(chan, DDEV, ERROR) << "Tx Dropped packets by HW! ("
752 << m_ctr[chan].tx_dropped_samples << " -> "
753 << m_ctr[chan].tx_dropped_samples +
754 status.droppedPackets
755 << ")";
756 m_ctr[chan].tx_dropped_events++;
757 }
758 m_ctr[chan].tx_dropped_samples += status.droppedPackets;
759
760 if (changed)
761 osmo_signal_dispatch(SS_DEVICE, S_DEVICE_COUNTER_CHANGE, &m_ctr[chan]);
762
763}
764
Harald Welte940738e2018-03-07 07:50:57 +0100765int LMSDevice::writeSamples(std::vector < short *>&bufs, int len,
766 bool * underrun, unsigned long long timestamp,
767 bool isControl)
768{
Vadim Yanitskiy92fc1862018-09-20 16:17:16 +0700769 int rc = 0;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200770 unsigned int i;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200771 lms_stream_meta_t tx_metadata = {};
772 tx_metadata.flushPartialPacket = false;
773 tx_metadata.waitForTimestamp = true;
Zydrunas Tamosevicius43f36782018-06-12 00:27:09 +0200774 tx_metadata.timestamp = timestamp - ts_offset; /* Shift Tx time by offset */
Harald Welte940738e2018-03-07 07:50:57 +0100775
776 if (isControl) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200777 LOGC(DDEV, ERROR) << "Control packets not supported";
Harald Welte940738e2018-03-07 07:50:57 +0100778 return 0;
779 }
780
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200781 if (bufs.size() != chans) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200782 LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size();
Harald Welte940738e2018-03-07 07:50:57 +0100783 return -1;
784 }
785
Harald Welte940738e2018-03-07 07:50:57 +0100786 *underrun = false;
787
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200788 for (i = 0; i<chans; i++) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200789 LOGCHAN(i, DDEV, DEBUG) << "send buffer of len " << len << " timestamp " << std::hex << tx_metadata.timestamp;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200790 thread_enable_cancel(false);
791 rc = LMS_SendStream(&m_lms_stream_tx[i], bufs[i], len, &tx_metadata, 100);
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200792 update_stream_stats_tx(i, underrun);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200793 thread_enable_cancel(true);
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200794 if (rc != len) {
795 LOGCHAN(i, DDEV, ERROR) << "LMS: Device Tx timed out (" << rc << " vs exp " << len << ").";
796 return -1;
797 }
Harald Welte940738e2018-03-07 07:50:57 +0100798 }
799
Harald Welte940738e2018-03-07 07:50:57 +0100800 return rc;
801}
802
803bool LMSDevice::updateAlignment(TIMESTAMP timestamp)
804{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200805 return true;
Harald Welte940738e2018-03-07 07:50:57 +0100806}
807
808bool LMSDevice::setTxFreq(double wFreq, size_t chan)
809{
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200810 LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz";
Pau Espin Pedrol138caaf2018-12-04 14:09:53 +0100811
Harald Welte940738e2018-03-07 07:50:57 +0100812 if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200813 LOGCHAN(chan, DDEV, ERROR) << "Error setting Tx Freq to " << wFreq << " Hz";
Harald Welte940738e2018-03-07 07:50:57 +0100814 return false;
815 }
816
817 return true;
818}
819
820bool LMSDevice::setRxFreq(double wFreq, size_t chan)
821{
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200822 LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz";
Pau Espin Pedrol138caaf2018-12-04 14:09:53 +0100823
Harald Welte940738e2018-03-07 07:50:57 +0100824 if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200825 LOGCHAN(chan, DDEV, ERROR) << "Error setting Rx Freq to " << wFreq << " Hz";
Harald Welte940738e2018-03-07 07:50:57 +0100826 return false;
827 }
828
829 return true;
830}
831
832RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
Harald Welte61707e82018-06-13 23:21:57 +0200833 InterfaceType iface, size_t chans, double lo_offset,
Harald Welte940738e2018-03-07 07:50:57 +0100834 const std::vector < std::string > &tx_paths,
835 const std::vector < std::string > &rx_paths)
836{
Harald Welteffb33012018-06-13 23:41:35 +0200837 if (tx_sps != rx_sps) {
Harald Welte5cc88582018-08-17 19:55:38 +0200838 LOGC(DDEV, ERROR) << "LMS Requires tx_sps == rx_sps";
Harald Welteffb33012018-06-13 23:41:35 +0200839 return NULL;
840 }
841 if (lo_offset != 0.0) {
Harald Welte5cc88582018-08-17 19:55:38 +0200842 LOGC(DDEV, ERROR) << "LMS doesn't support lo_offset";
Harald Welteffb33012018-06-13 23:41:35 +0200843 return NULL;
844 }
Harald Welte61707e82018-06-13 23:21:57 +0200845 return new LMSDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
Harald Welte940738e2018-03-07 07:50:57 +0100846}