blob: 451d2b9e2987d10b0db9153f5abd6b36755cc001 [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>
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +010023
24#include <map>
25
Harald Welte940738e2018-03-07 07:50:57 +010026#include "Logger.h"
27#include "Threads.h"
28#include "LMSDevice.h"
Oliver Smith871713b2018-12-10 17:10:36 +010029#include "Utils.h"
Harald Welte940738e2018-03-07 07:50:57 +010030
31#include <lime/LimeSuite.h>
32
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +020033extern "C" {
Vadim Yanitskiya0d862b2020-10-24 19:18:13 +070034#include "trx_vty.h"
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +020035#include "osmo_signal.h"
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020036#include <osmocom/core/utils.h>
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +020037}
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020038
Harald Welte940738e2018-03-07 07:50:57 +010039#ifdef HAVE_CONFIG_H
40#include "config.h"
41#endif
42
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020043#define MAX_ANTENNA_LIST_SIZE 10
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020044#define GSM_CARRIER_BW 270000.0 /* 270kHz */
45#define LMS_MIN_BW_SUPPORTED 2.5e6 /* 2.5mHz, minimum supported by LMS */
46#define LMS_CALIBRATE_BW_HZ OSMO_MAX(GSM_CARRIER_BW, LMS_MIN_BW_SUPPORTED)
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +020047#define SAMPLE_BUF_SZ (1 << 20) /* Size of Rx timestamp based Ring buffer, in bytes */
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +020048
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +010049
50/* Device Name Prefixes as presented by LimeSuite API LMS_GetDeviceInfo(): */
51#define LMS_DEV_SDR_USB_PREFIX_NAME "LimeSDR-USB"
52#define LMS_DEV_SDR_MINI_PREFIX_NAME "LimeSDR-Mini"
53#define LMS_DEV_NET_MICRO_PREFIX_NAME "LimeNET-Micro"
54
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +010055
Ericc0f78a32023-05-12 13:00:14 +020056
57static const dev_map_t dev_param_map {
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +020058 { LMS_DEV_SDR_USB, { true, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_SDR_USB_PREFIX_NAME } },
59 { LMS_DEV_SDR_MINI, { false, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 8.2e-5, LMS_DEV_SDR_MINI_PREFIX_NAME } },
60 { LMS_DEV_NET_MICRO, { true, false, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_NET_MICRO_PREFIX_NAME } },
61 { LMS_DEV_UNKNOWN, { true, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, "UNKNOWN" } },
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +010062};
63
Ericc0f78a32023-05-12 13:00:14 +020064static const power_map_t dev_band_nom_power_param_map {
Pau Espin Pedrole91544d2020-10-13 17:03:37 +020065 { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_850), { 73.0, 11.2, -6.0 } },
66 { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_900), { 73.0, 10.8, -6.0 } },
67 { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1800), { 65.0, -3.5, -17.0 } }, /* FIXME: OS#4583: 1800Mhz is failing above TxGain=65, which is around -3.5dBm (already < 0 dBm) */
68 { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1900), { 73.0, 1.7, -17.0 } }, /* FIXME: OS#4583: 1900MHz is failing in all TxGain values */
69 { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_850), { 66.0, 3.1, -6.0 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */
70 { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_900), { 66.0, 2.8, -6.0 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */
71 { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1800), { 66.0, -11.6, -17.0 } }, /* OS#4583: Any of BAND1 or BAND2 is fine */
72 { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1900), { 66.0, -9.2, -17.0 } }, /* FIXME: OS#4583: Ensure BAND1 is used at startup */
73 { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_850), { 71.0, 6.8, -6.0 } },
74 { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_900), { 71.0, 6.8, -6.0 } },
75 { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1800), { 65.0, -10.5, -17.0 } }, /* OS#4583: TxGain=71 (-4.4dBm) FAIL rms phase errors ~10° */
76 { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1900), { 71.0, -6.3, -17.0 } }, /* FIXME: OS#4583: all FAIL, BAND1/BAND2 rms phase errors >23° */
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +020077};
78
79/* So far measurements done for B210 show really close to linear relationship
80 * between gain and real output power, so we simply adjust the measured offset
81 */
82static double TxGain2TxPower(const dev_band_desc &desc, double tx_gain_db)
83{
84 return desc.nom_out_tx_power - (desc.nom_lms_tx_gain - tx_gain_db);
85}
86static double TxPower2TxGain(const dev_band_desc &desc, double tx_power_dbm)
87{
88 return desc.nom_lms_tx_gain - (desc.nom_out_tx_power - tx_power_dbm);
89}
90
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +010091static enum lms_dev_type parse_dev_type(lms_device_t *m_lms_dev)
92{
93 std::map<enum lms_dev_type, struct dev_desc>::const_iterator it = dev_param_map.begin();
94
95 const lms_dev_info_t* device_info = LMS_GetDeviceInfo(m_lms_dev);
96
97 while (it != dev_param_map.end())
98 {
99 enum lms_dev_type dev_type = it->first;
100 struct dev_desc desc = it->second;
101
Ericc0f78a32023-05-12 13:00:14 +0200102 if (strncmp(device_info->deviceName, desc.desc_str.c_str(), desc.desc_str.length()) == 0) {
103 LOGC(DDEV, INFO) << "Device identified as " << desc.desc_str;
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +0100104 return dev_type;
105 }
106 it++;
107 }
108 return LMS_DEV_UNKNOWN;
109}
110
Eric19e134a2023-05-10 23:50:38 +0200111LMSDevice::LMSDevice(InterfaceType iface, const struct trx_cfg *cfg)
Ericc0f78a32023-05-12 13:00:14 +0200112 : RadioDevice(iface, cfg),
113 band_manager(m_dev_type, dev_band_nom_power_param_map, dev_param_map, {LMS_DEV_SDR_USB, GSM_BAND_850}), m_lms_dev(NULL),
114 started(false), m_dev_type(LMS_DEV_UNKNOWN)
Harald Welte940738e2018-03-07 07:50:57 +0100115{
Harald Welte5cc88582018-08-17 19:55:38 +0200116 LOGC(DDEV, INFO) << "creating LMS device...";
Harald Welte940738e2018-03-07 07:50:57 +0100117
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200118 m_lms_stream_rx.resize(chans);
119 m_lms_stream_tx.resize(chans);
Pau Espin Pedrol705a3482019-09-13 16:51:48 +0200120 rx_gains.resize(chans);
121 tx_gains.resize(chans);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200122
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200123 rx_buffers.resize(chans);
Pau Espin Pedrole7f6a272020-01-14 17:54:29 +0100124
125 /* Set up per-channel Rx timestamp based Ring buffers */
126 for (size_t i = 0; i < rx_buffers.size(); i++)
127 rx_buffers[i] = new smpl_buf(SAMPLE_BUF_SZ / sizeof(uint32_t));
128
Harald Welte940738e2018-03-07 07:50:57 +0100129}
130
Pau Espin Pedrol5cea18e2018-12-03 18:17:18 +0100131LMSDevice::~LMSDevice()
132{
Joachim Steiger2d130fb2019-04-16 16:22:23 +0200133 unsigned int i;
Pau Espin Pedrol5cea18e2018-12-03 18:17:18 +0100134 LOGC(DDEV, INFO) << "Closing LMS device";
135 if (m_lms_dev) {
Joachim Steiger2d130fb2019-04-16 16:22:23 +0200136 /* disable all channels */
137 for (i=0; i<chans; i++) {
138 LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, false);
139 LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, false);
140 }
Pau Espin Pedrol5cea18e2018-12-03 18:17:18 +0100141 LMS_Close(m_lms_dev);
142 m_lms_dev = NULL;
143 }
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200144
145 for (size_t i = 0; i < rx_buffers.size(); i++)
146 delete rx_buffers[i];
Pau Espin Pedrol5cea18e2018-12-03 18:17:18 +0100147}
148
Harald Welte940738e2018-03-07 07:50:57 +0100149static void lms_log_callback(int lvl, const char *msg)
150{
151 /* map lime specific log levels */
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200152 static const int lvl_map[5] = {
Harald Welte940738e2018-03-07 07:50:57 +0100153 [0] = LOGL_FATAL,
Pau Espin Pedrol32b3c2e2018-11-23 14:39:06 +0100154 [LMS_LOG_ERROR] = LOGL_ERROR,
155 [LMS_LOG_WARNING] = LOGL_NOTICE,
156 [LMS_LOG_INFO] = LOGL_INFO,
157 [LMS_LOG_DEBUG] = LOGL_DEBUG,
Harald Welte940738e2018-03-07 07:50:57 +0100158 };
159 /* protect against future higher log level values (lower importance) */
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200160 if ((unsigned int) lvl >= ARRAY_SIZE(lvl_map))
Harald Welte940738e2018-03-07 07:50:57 +0100161 lvl = ARRAY_SIZE(lvl_map)-1;
162
Pau Espin Pedrolaebbfe02020-01-02 16:39:11 +0100163 LOGLV(DDEVDRV, lvl_map[lvl]) << msg;
Harald Welte940738e2018-03-07 07:50:57 +0100164}
165
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +0200166static void print_range(const char* name, lms_range_t *range)
167{
Harald Welte5cc88582018-08-17 19:55:38 +0200168 LOGC(DDEV, INFO) << name << ": Min=" << range->min << " Max=" << range->max
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +0200169 << " Step=" << range->step;
170}
171
Oliver Smith871713b2018-12-10 17:10:36 +0100172/*! Find the device string that matches all filters from \a args.
173 * \param[in] info_list device addresses found by LMS_GetDeviceList()
174 * \param[in] count length of info_list
175 * \param[in] args dev-args value from osmo-trx.cfg, containing comma separated key=value pairs
176 * \return index of first matching device or -1 (no match) */
177int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::string &args)
178{
179 unsigned int i, j;
Vadim Yanitskiy6b4acc12020-10-24 07:05:22 +0700180 std::vector<std::string> filters;
Oliver Smith871713b2018-12-10 17:10:36 +0100181
182 filters = comma_delimited_to_vector(args.c_str());
183
184 /* iterate over device addresses */
185 for (i=0; i < count; i++) {
186 /* check if all filters match */
187 bool match = true;
188 for (j=0; j < filters.size(); j++) {
189 if (!strstr(info_list[i], filters[j].c_str())) {
190 match = false;
191 break;
192 }
193 }
194
195 if (match)
196 return i;
197 }
198 return -1;
199}
200
Eric19e134a2023-05-10 23:50:38 +0200201int LMSDevice::open()
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200202{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200203 lms_info_str_t* info_list;
Joachim Steiger4ce45552019-04-16 16:35:53 +0200204 lms_range_t range_sr;
205 float_type sr_host, sr_rf;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200206 unsigned int i, n;
Oliver Smith871713b2018-12-10 17:10:36 +0100207 int rc, dev_id;
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +0100208 struct dev_desc dev_desc;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200209
Harald Welte5cc88582018-08-17 19:55:38 +0200210 LOGC(DDEV, INFO) << "Opening LMS device..";
Harald Welte940738e2018-03-07 07:50:57 +0100211
212 LMS_RegisterLogHandler(&lms_log_callback);
213
Vadim Yanitskiy019d6982021-10-25 00:30:27 +0300214 if ((rc = LMS_GetDeviceList(NULL)) < 0)
Harald Welte5cc88582018-08-17 19:55:38 +0200215 LOGC(DDEV, ERROR) << "LMS_GetDeviceList(NULL) failed";
Vadim Yanitskiy019d6982021-10-25 00:30:27 +0300216 LOGC(DDEV, INFO) << "Devices found: " << rc;
217 if (rc < 1)
Harald Welte62b79002018-06-13 23:32:42 +0200218 return -1;
Vadim Yanitskiy019d6982021-10-25 00:30:27 +0300219 n = rc;
Harald Welte940738e2018-03-07 07:50:57 +0100220
Harald Welte62b79002018-06-13 23:32:42 +0200221 info_list = new lms_info_str_t[n];
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200222
Harald Welte62b79002018-06-13 23:32:42 +0200223 if (LMS_GetDeviceList(info_list) < 0)
Harald Welte5cc88582018-08-17 19:55:38 +0200224 LOGC(DDEV, ERROR) << "LMS_GetDeviceList(info_list) failed";
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200225
Harald Welte62b79002018-06-13 23:32:42 +0200226 for (i = 0; i < n; i++)
Harald Welte5cc88582018-08-17 19:55:38 +0200227 LOGC(DDEV, INFO) << "Device [" << i << "]: " << info_list[i];
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200228
Eric19e134a2023-05-10 23:50:38 +0200229 dev_id = info_list_find(info_list, n, cfg->dev_args);
Oliver Smith871713b2018-12-10 17:10:36 +0100230 if (dev_id == -1) {
Eric19e134a2023-05-10 23:50:38 +0200231 LOGC(DDEV, ERROR) << "No LMS device found with address '" << cfg->dev_args << "'";
Oliver Smith871713b2018-12-10 17:10:36 +0100232 delete[] info_list;
233 return -1;
234 }
235
236 LOGC(DDEV, INFO) << "Using device[" << dev_id << "]";
237 rc = LMS_Open(&m_lms_dev, info_list[dev_id], NULL);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200238 if (rc != 0) {
Harald Welte5cc88582018-08-17 19:55:38 +0200239 LOGC(DDEV, ERROR) << "LMS_GetDeviceList() failed)";
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200240 delete [] info_list;
241 return -1;
242 }
243
244 delete [] info_list;
245
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +0100246 m_dev_type = parse_dev_type(m_lms_dev);
247 dev_desc = dev_param_map.at(m_dev_type);
Joachim Steiger28752902019-04-16 17:10:13 +0200248
Eric19e134a2023-05-10 23:50:38 +0200249 if ((cfg->clock_ref != REF_EXTERNAL) && (cfg->clock_ref != REF_INTERNAL)) {
Joachim Steiger28752902019-04-16 17:10:13 +0200250 LOGC(DDEV, ERROR) << "Invalid reference type";
251 goto out_close;
252 }
253
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +0100254 /* if reference clock is external, setup must happen _before_ calling LMS_Init */
Eric19e134a2023-05-10 23:50:38 +0200255 if (cfg->clock_ref == REF_EXTERNAL) {
Joachim Steiger28752902019-04-16 17:10:13 +0200256 LOGC(DDEV, INFO) << "Setting External clock reference to 10MHz";
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +0100257 /* FIXME: Assume an external 10 MHz reference clock. make
258 external reference frequency configurable */
259 if (!do_clock_src_freq(REF_EXTERNAL, 10000000.0))
Joachim Steiger28752902019-04-16 17:10:13 +0200260 goto out_close;
261 }
262
Harald Welte5cc88582018-08-17 19:55:38 +0200263 LOGC(DDEV, INFO) << "Init LMS device";
Harald Welte9cb4f272018-04-28 21:38:58 +0200264 if (LMS_Init(m_lms_dev) != 0) {
Harald Welte5cc88582018-08-17 19:55:38 +0200265 LOGC(DDEV, ERROR) << "LMS_Init() failed";
Pau Espin Pedroled361f92018-12-04 12:42:30 +0100266 goto out_close;
Harald Welte9cb4f272018-04-28 21:38:58 +0200267 }
268
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +0100269 /* if reference clock is internal, setup must happen _after_ calling LMS_Init */
Eric19e134a2023-05-10 23:50:38 +0200270 if (cfg->clock_ref == REF_INTERNAL) {
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +0100271 LOGC(DDEV, INFO) << "Setting Internal clock reference";
272 /* Internal freq param is not used */
273 if (!do_clock_src_freq(REF_INTERNAL, 0))
274 goto out_close;
275 }
Joachim Steiger28752902019-04-16 17:10:13 +0200276
Joachim Steiger2d130fb2019-04-16 16:22:23 +0200277 /* enable all used channels */
278 for (i=0; i<chans; i++) {
279 if (LMS_EnableChannel(m_lms_dev, LMS_CH_RX, i, true) < 0)
280 goto out_close;
281 if (LMS_EnableChannel(m_lms_dev, LMS_CH_TX, i, true) < 0)
282 goto out_close;
283 }
284
Joachim Steigerc785fb12019-04-16 19:04:37 +0200285 /* set samplerate */
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +0200286 if (LMS_GetSampleRateRange(m_lms_dev, LMS_CH_RX, &range_sr))
Harald Weltea4381142018-04-28 21:41:07 +0200287 goto out_close;
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +0200288 print_range("Sample Rate", &range_sr);
Harald Weltea4381142018-04-28 21:41:07 +0200289
Pau Espin Pedrol05698452020-01-14 19:02:40 +0100290 if (iface == MULTI_ARFCN)
291 sr_host = dev_desc.rate_multiarfcn * tx_sps;
292 else
293 sr_host = dev_desc.rate * tx_sps;
Pau Espin Pedrolc69b87f2020-01-14 18:50:36 +0100294 LOGC(DDEV, INFO) << "Setting sample rate to " << sr_host << " " << tx_sps;
295 if (LMS_SetSampleRate(m_lms_dev, sr_host, 32) < 0)
Harald Welte940738e2018-03-07 07:50:57 +0100296 goto out_close;
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +0200297
Harald Weltea4381142018-04-28 21:41:07 +0200298 if (LMS_GetSampleRate(m_lms_dev, LMS_CH_RX, 0, &sr_host, &sr_rf))
299 goto out_close;
Harald Welte5cc88582018-08-17 19:55:38 +0200300 LOGC(DDEV, INFO) << "Sample Rate: Host=" << sr_host << " RF=" << sr_rf;
Pau Espin Pedrol6493dc82018-05-29 19:00:30 +0200301
Pau Espin Pedrol05698452020-01-14 19:02:40 +0100302 if (iface == MULTI_ARFCN)
303 ts_offset = static_cast<TIMESTAMP>(dev_desc.ts_offset_coef_multiarfcn * sr_host);
304 else
305 ts_offset = static_cast<TIMESTAMP>(dev_desc.ts_offset_coef * sr_host);
Harald Welte940738e2018-03-07 07:50:57 +0100306
Joachim Steigerc785fb12019-04-16 19:04:37 +0200307 /* configure antennas */
Harald Weltecfb9dac2018-06-13 21:56:24 +0200308 if (!set_antennas()) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200309 LOGC(DDEV, FATAL) << "LMS antenna setting failed";
Joachim Steigerc785fb12019-04-16 19:04:37 +0200310 goto out_close;
Harald Weltecfb9dac2018-06-13 21:56:24 +0200311 }
312
Pau Espin Pedrol05698452020-01-14 19:02:40 +0100313 return iface == MULTI_ARFCN ? MULTI_ARFCN : NORMAL;
Harald Welte940738e2018-03-07 07:50:57 +0100314
315out_close:
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200316 LOGC(DDEV, FATAL) << "Error in LMS open, closing: " << LMS_GetLastErrorMessage();
Harald Welte940738e2018-03-07 07:50:57 +0100317 LMS_Close(m_lms_dev);
Pau Espin Pedrol5cea18e2018-12-03 18:17:18 +0100318 m_lms_dev = NULL;
Harald Welte940738e2018-03-07 07:50:57 +0100319 return -1;
320}
321
322bool LMSDevice::start()
323{
Harald Welte5cc88582018-08-17 19:55:38 +0200324 LOGC(DDEV, INFO) << "starting LMS...";
Harald Welte940738e2018-03-07 07:50:57 +0100325
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200326 unsigned int i;
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +0200327 dev_band_desc desc;
Harald Welte940738e2018-03-07 07:50:57 +0100328
Pau Espin Pedrol69869bd2018-12-03 11:19:52 +0100329 if (started) {
330 LOGC(DDEV, ERR) << "Device already started";
331 return false;
332 }
333
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +0200334 get_dev_band_desc(desc);
335
Zydrunas Tamosevicius0494d052018-06-12 00:29:16 +0200336 /* configure the channels/streams */
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200337 for (i=0; i<chans; i++) {
Joachim Steiger28752902019-04-16 17:10:13 +0200338 /* Set gains for calibration/filter setup */
339 /* TX gain to maximum */
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +0200340 LMS_SetGaindB(m_lms_dev, LMS_CH_TX, i, TxPower2TxGain(desc, desc.nom_out_tx_power));
Joachim Steiger28752902019-04-16 17:10:13 +0200341 /* RX gain to midpoint */
Harald Welte55928f22018-11-26 19:26:52 +0100342 setRxGain((minRxGain() + maxRxGain()) / 2, i);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200343
Joachim Steiger4ce45552019-04-16 16:35:53 +0200344 /* set up Rx and Tx filters */
345 if (!do_filters(i))
346 return false;
347 /* Perform Rx and Tx calibration */
348 if (!do_calib(i))
349 return false;
350
Joachim Steigerc785fb12019-04-16 19:04:37 +0200351 /* configure Streams */
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200352 m_lms_stream_rx[i] = {};
353 m_lms_stream_rx[i].isTx = false;
354 m_lms_stream_rx[i].channel = i;
355 m_lms_stream_rx[i].fifoSize = 1024 * 1024;
356 m_lms_stream_rx[i].throughputVsLatency = 0.3;
357 m_lms_stream_rx[i].dataFmt = lms_stream_t::LMS_FMT_I16;
358
359 m_lms_stream_tx[i] = {};
360 m_lms_stream_tx[i].isTx = true;
361 m_lms_stream_tx[i].channel = i;
362 m_lms_stream_tx[i].fifoSize = 1024 * 1024;
363 m_lms_stream_tx[i].throughputVsLatency = 0.3;
364 m_lms_stream_tx[i].dataFmt = lms_stream_t::LMS_FMT_I16;
365
366 if (LMS_SetupStream(m_lms_dev, &m_lms_stream_rx[i]) < 0)
367 return false;
368
369 if (LMS_SetupStream(m_lms_dev, &m_lms_stream_tx[i]) < 0)
370 return false;
Zydrunas Tamosevicius0494d052018-06-12 00:29:16 +0200371 }
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200372
Zydrunas Tamosevicius0494d052018-06-12 00:29:16 +0200373 /* now start the streams in a second loop, as we can no longer call
374 * LMS_SetupStream() after LMS_StartStream() of the first stream */
375 for (i = 0; i < chans; i++) {
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200376 if (LMS_StartStream(&m_lms_stream_rx[i]) < 0)
377 return false;
378
379 if (LMS_StartStream(&m_lms_stream_tx[i]) < 0)
380 return false;
Harald Welte940738e2018-03-07 07:50:57 +0100381 }
382
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200383 flush_recv(10);
Harald Welte940738e2018-03-07 07:50:57 +0100384
385 started = true;
386 return true;
387}
388
389bool LMSDevice::stop()
390{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200391 unsigned int i;
392
Harald Welte940738e2018-03-07 07:50:57 +0100393 if (!started)
394 return true;
395
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200396 for (i=0; i<chans; i++) {
397 LMS_StopStream(&m_lms_stream_tx[i]);
398 LMS_StopStream(&m_lms_stream_rx[i]);
Pau Espin Pedrolb4ea7b52018-12-03 11:34:23 +0100399 }
Harald Welte940738e2018-03-07 07:50:57 +0100400
Pau Espin Pedrolb4ea7b52018-12-03 11:34:23 +0100401 for (i=0; i<chans; i++) {
402 LMS_DestroyStream(m_lms_dev, &m_lms_stream_tx[i]);
403 LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200404 }
Harald Welte940738e2018-03-07 07:50:57 +0100405
Ericc0f78a32023-05-12 13:00:14 +0200406 band_reset();
Pau Espin Pedrolbb2cb9d2021-09-21 13:52:46 +0200407
Pau Espin Pedrol69869bd2018-12-03 11:19:52 +0100408 started = false;
Harald Welte940738e2018-03-07 07:50:57 +0100409 return true;
410}
411
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +0100412bool LMSDevice::do_clock_src_freq(enum ReferenceType ref, double freq)
413{
414 struct dev_desc dev_desc = dev_param_map.at(m_dev_type);
415 size_t lms_clk_id;
416
417 switch (ref) {
418 case REF_EXTERNAL:
419 lms_clk_id = LMS_CLOCK_EXTREF;
420 break;
421 case REF_INTERNAL:
422 if (!dev_desc.clock_src_int_usable) {
Ericc0f78a32023-05-12 13:00:14 +0200423 LOGC(DDEV, ERROR)
424 << "Device type " << dev_desc.desc_str << " doesn't support internal reference clock";
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +0100425 return false;
426 }
427 /* According to lms using LMS_CLOCK_EXTREF with a
428 frequency <= 0 is the correct way to set clock to
429 internal reference */
430 lms_clk_id = LMS_CLOCK_EXTREF;
431 freq = -1;
432 break;
433 default:
434 LOGC(DDEV, ERROR) << "Invalid reference type " << get_value_string(clock_ref_names, ref);
435 return false;
436 }
437
438 if (dev_desc.clock_src_switchable) {
439 if (LMS_SetClockFreq(m_lms_dev, lms_clk_id, freq) < 0)
440 return false;
441 } else {
Ericc0f78a32023-05-12 13:00:14 +0200442 LOGC(DDEV, INFO)
443 << "Device type " << dev_desc.desc_str << " doesn't support switching clock source through SW";
Pau Espin Pedrola7bf6cd2020-01-14 17:52:15 +0100444 }
445
446 return true;
447}
448
Joachim Steiger4ce45552019-04-16 16:35:53 +0200449/* do rx/tx calibration - depends on gain, freq and bw */
450bool LMSDevice::do_calib(size_t chan)
451{
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200452 LOGCHAN(chan, DDEV, INFO) << "Calibrating";
Joachim Steiger4ce45552019-04-16 16:35:53 +0200453 if (LMS_Calibrate(m_lms_dev, LMS_CH_RX, chan, LMS_CALIBRATE_BW_HZ, 0) < 0)
454 return false;
455 if (LMS_Calibrate(m_lms_dev, LMS_CH_TX, chan, LMS_CALIBRATE_BW_HZ, 0) < 0)
456 return false;
457 return true;
458}
459
460/* do rx/tx filter config - depends on bw only? */
461bool LMSDevice::do_filters(size_t chan)
462{
463 lms_range_t range_lpfbw_rx, range_lpfbw_tx;
464 float_type lpfbw_rx, lpfbw_tx;
465
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200466 LOGCHAN(chan, DDEV, INFO) << "Setting filters";
Joachim Steiger4ce45552019-04-16 16:35:53 +0200467 if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_rx))
468 return false;
469 print_range("LPFBWRange Rx", &range_lpfbw_rx);
470 if (LMS_GetLPFBWRange(m_lms_dev, LMS_CH_RX, &range_lpfbw_tx))
471 return false;
472 print_range("LPFBWRange Tx", &range_lpfbw_tx);
473
474 lpfbw_rx = OSMO_MIN(OSMO_MAX(1.4001e6, range_lpfbw_rx.min), range_lpfbw_rx.max);
475 lpfbw_tx = OSMO_MIN(OSMO_MAX(5.2e6, range_lpfbw_tx.min), range_lpfbw_tx.max);
476
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200477 LOGCHAN(chan, DDEV, INFO) << "LPFBW: Rx=" << lpfbw_rx << " Tx=" << lpfbw_tx;
Joachim Steiger4ce45552019-04-16 16:35:53 +0200478
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200479 LOGCHAN(chan, DDEV, INFO) << "Setting LPFBW";
Joachim Steiger4ce45552019-04-16 16:35:53 +0200480 if (LMS_SetLPFBW(m_lms_dev, LMS_CH_RX, chan, lpfbw_rx) < 0)
481 return false;
482 if (LMS_SetLPFBW(m_lms_dev, LMS_CH_TX, chan, lpfbw_tx) < 0)
483 return false;
484 return true;
485}
486
Harald Welte940738e2018-03-07 07:50:57 +0100487double LMSDevice::maxRxGain()
488{
Pau Espin Pedrol587916e2018-05-08 18:46:28 +0200489 return 73.0;
Harald Welte940738e2018-03-07 07:50:57 +0100490}
491
492double LMSDevice::minRxGain()
493{
494 return 0.0;
495}
496
Harald Welte940738e2018-03-07 07:50:57 +0100497double LMSDevice::setRxGain(double dB, size_t chan)
498{
Harald Welte940738e2018-03-07 07:50:57 +0100499 if (dB > maxRxGain())
500 dB = maxRxGain();
501 if (dB < minRxGain())
502 dB = minRxGain();
503
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200504 LOGCHAN(chan, DDEV, NOTICE) << "Setting RX gain to " << dB << " dB";
Harald Welte940738e2018-03-07 07:50:57 +0100505
506 if (LMS_SetGaindB(m_lms_dev, LMS_CH_RX, chan, dB) < 0)
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200507 LOGCHAN(chan, DDEV, ERR) << "Error setting RX gain to " << dB << " dB";
Pau Espin Pedrol705a3482019-09-13 16:51:48 +0200508 else
509 rx_gains[chan] = dB;
510 return rx_gains[chan];
Harald Welte940738e2018-03-07 07:50:57 +0100511}
512
Pau Espin Pedrole91544d2020-10-13 17:03:37 +0200513double LMSDevice::rssiOffset(size_t chan)
514{
515 double rssiOffset;
516 dev_band_desc desc;
517
518 if (chan >= rx_gains.size()) {
519 LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
520 return 0.0f;
521 }
522
523 get_dev_band_desc(desc);
524 rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel;
525 return rssiOffset;
526}
527
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +0200528double LMSDevice::setPowerAttenuation(int atten, size_t chan)
529{
Pau Espin Pedrol58d80a02020-06-19 19:29:59 +0200530 double tx_power, dB;
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +0200531 dev_band_desc desc;
532
533 if (chan >= tx_gains.size()) {
534 LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
535 return 0.0f;
536 }
537
538 get_dev_band_desc(desc);
Pau Espin Pedrol58d80a02020-06-19 19:29:59 +0200539 tx_power = desc.nom_out_tx_power - atten;
540 dB = TxPower2TxGain(desc, tx_power);
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +0200541
Pau Espin Pedrol58d80a02020-06-19 19:29:59 +0200542 LOGCHAN(chan, DDEV, NOTICE) << "Setting TX gain to " << dB << " dB (~" << tx_power << " dBm)";
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +0200543
544 if (LMS_SetGaindB(m_lms_dev, LMS_CH_TX, chan, dB) < 0)
Pau Espin Pedrol58d80a02020-06-19 19:29:59 +0200545 LOGCHAN(chan, DDEV, ERR) << "Error setting TX gain to " << dB << " dB (~" << tx_power << " dBm)";
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +0200546 else
547 tx_gains[chan] = dB;
548 return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]);
549}
550
551double LMSDevice::getPowerAttenuation(size_t chan) {
552 dev_band_desc desc;
553 if (chan >= tx_gains.size()) {
554 LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
555 return 0.0f;
556 }
557
558 get_dev_band_desc(desc);
559 return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]);
560}
561
Pau Espin Pedrol0e09e7c2020-05-29 16:39:07 +0200562int LMSDevice::getNominalTxPower(size_t chan)
563{
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +0200564 dev_band_desc desc;
565 get_dev_band_desc(desc);
566
567 return desc.nom_out_tx_power;
Pau Espin Pedrol0e09e7c2020-05-29 16:39:07 +0200568}
569
Pau Espin Pedrole0010fa2019-08-26 17:10:27 +0200570void LMSDevice::log_ant_list(bool dir_tx, size_t chan, std::ostringstream& os)
571{
572 lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
573 int num_names;
574 int i;
575
576 num_names = LMS_GetAntennaList(m_lms_dev, dir_tx, chan, name_list);
577 for (i = 0; i < num_names; i++) {
578 if (i)
579 os << ", ";
580 os << "'" << name_list[i] << "'";
581 }
582}
583
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200584int LMSDevice::get_ant_idx(const std::string & name, bool dir_tx, size_t chan)
Harald Welte940738e2018-03-07 07:50:57 +0100585{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200586 lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
587 const char* c_name = name.c_str();
Harald Welte940738e2018-03-07 07:50:57 +0100588 int num_names;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200589 int i;
590
591 num_names = LMS_GetAntennaList(m_lms_dev, dir_tx, chan, name_list);
Harald Welte940738e2018-03-07 07:50:57 +0100592 for (i = 0; i < num_names; i++) {
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200593 if (!strcmp(c_name, name_list[i]))
Harald Welte940738e2018-03-07 07:50:57 +0100594 return i;
595 }
596 return -1;
597}
598
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200599bool LMSDevice::flush_recv(size_t num_pkts)
600{
601 #define CHUNK 625
Harald Weltece70ba52018-06-13 22:47:48 +0200602 int len = CHUNK * tx_sps;
Pau Espin Pedrol541496b2019-04-25 19:22:07 +0200603 short *buffer = (short*) alloca(sizeof(short) * len * 2);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200604 int rc;
605 lms_stream_meta_t rx_metadata = {};
606 rx_metadata.flushPartialPacket = false;
607 rx_metadata.waitForTimestamp = false;
608
609 ts_initial = 0;
610
611 while (!ts_initial || (num_pkts-- > 0)) {
612 rc = LMS_RecvStream(&m_lms_stream_rx[0], &buffer[0], len, &rx_metadata, 100);
Harald Welte5cc88582018-08-17 19:55:38 +0200613 LOGC(DDEV, DEBUG) << "Flush: Recv buffer of len " << rc << " at " << std::hex << rx_metadata.timestamp;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200614 if (rc != len) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200615 LOGC(DDEV, ERROR) << "Flush: Device receive timed out";
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200616 return false;
617 }
618
Harald Welte68f05412018-04-28 21:58:03 +0200619 ts_initial = rx_metadata.timestamp + len;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200620 }
621
Harald Welte5cc88582018-08-17 19:55:38 +0200622 LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200623 return true;
624}
625
Harald Welte940738e2018-03-07 07:50:57 +0100626bool LMSDevice::setRxAntenna(const std::string & ant, size_t chan)
627{
628 int idx;
629
630 if (chan >= rx_paths.size()) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200631 LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
Harald Welte940738e2018-03-07 07:50:57 +0100632 return false;
633 }
634
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200635 idx = get_ant_idx(ant, LMS_CH_RX, chan);
Harald Welte940738e2018-03-07 07:50:57 +0100636 if (idx < 0) {
Pau Espin Pedrole0010fa2019-08-26 17:10:27 +0200637 std::ostringstream os;
638 LOGCHAN(chan, DDEV, ERROR) << "Invalid Rx Antenna: " << ant;
639 log_ant_list(LMS_CH_RX, chan, os);
640 LOGCHAN(chan, DDEV, NOTICE) << "Available Rx Antennas: " << os;
Harald Welte940738e2018-03-07 07:50:57 +0100641 return false;
642 }
643
644 if (LMS_SetAntenna(m_lms_dev, LMS_CH_RX, chan, idx) < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200645 LOGCHAN(chan, DDEV, ERROR) << "Unable to set Rx Antenna";
Harald Welte940738e2018-03-07 07:50:57 +0100646 }
647
648 return true;
649}
650
651std::string LMSDevice::getRxAntenna(size_t chan)
652{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200653 lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
654 int idx;
655
Harald Welte940738e2018-03-07 07:50:57 +0100656 if (chan >= rx_paths.size()) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200657 LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
Harald Welte940738e2018-03-07 07:50:57 +0100658 return "";
659 }
660
661 idx = LMS_GetAntenna(m_lms_dev, LMS_CH_RX, chan);
662 if (idx < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200663 LOGCHAN(chan, DDEV, ERROR) << "Error getting Rx Antenna";
Harald Welte940738e2018-03-07 07:50:57 +0100664 return "";
665 }
666
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200667 if (LMS_GetAntennaList(m_lms_dev, LMS_CH_RX, chan, name_list) < idx) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200668 LOGCHAN(chan, DDEV, ERROR) << "Error getting Rx Antenna List";
Harald Welte940738e2018-03-07 07:50:57 +0100669 return "";
670 }
671
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200672 return name_list[idx];
Harald Welte940738e2018-03-07 07:50:57 +0100673}
674
675bool LMSDevice::setTxAntenna(const std::string & ant, size_t chan)
676{
677 int idx;
678
679 if (chan >= tx_paths.size()) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200680 LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
Harald Welte940738e2018-03-07 07:50:57 +0100681 return false;
682 }
683
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200684 idx = get_ant_idx(ant, LMS_CH_TX, chan);
Harald Welte940738e2018-03-07 07:50:57 +0100685 if (idx < 0) {
Pau Espin Pedrole0010fa2019-08-26 17:10:27 +0200686 std::ostringstream os;
687 LOGCHAN(chan, DDEV, ERROR) << "Invalid Tx Antenna: " << ant;
688 log_ant_list(LMS_CH_TX, chan, os);
689 LOGCHAN(chan, DDEV, NOTICE) << "Available Tx Antennas: " << os;
Harald Welte940738e2018-03-07 07:50:57 +0100690 return false;
691 }
692
693 if (LMS_SetAntenna(m_lms_dev, LMS_CH_TX, chan, idx) < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200694 LOGCHAN(chan, DDEV, ERROR) << "Unable to set Rx Antenna";
Harald Welte940738e2018-03-07 07:50:57 +0100695 }
696
697 return true;
698}
699
700std::string LMSDevice::getTxAntenna(size_t chan)
701{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200702 lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */
Harald Welte940738e2018-03-07 07:50:57 +0100703 int idx;
704
705 if (chan >= tx_paths.size()) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200706 LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan;
Harald Welte940738e2018-03-07 07:50:57 +0100707 return "";
708 }
709
710 idx = LMS_GetAntenna(m_lms_dev, LMS_CH_TX, chan);
711 if (idx < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200712 LOGCHAN(chan, DDEV, ERROR) << "Error getting Tx Antenna";
Harald Welte940738e2018-03-07 07:50:57 +0100713 return "";
714 }
715
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200716 if (LMS_GetAntennaList(m_lms_dev, LMS_CH_TX, chan, name_list) < idx) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200717 LOGCHAN(chan, DDEV, ERROR) << "Error getting Tx Antenna List";
Harald Welte940738e2018-03-07 07:50:57 +0100718 return "";
719 }
720
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200721 return name_list[idx];
722}
723
724bool LMSDevice::requiresRadioAlign()
725{
726 return false;
727}
728
729GSM::Time LMSDevice::minLatency() {
Joachim Steiger6c5f4ba2019-04-16 16:13:02 +0200730 /* UNUSED on limesdr (only used on usrp1/2) */
731 return GSM::Time(0,0);
Harald Welte940738e2018-03-07 07:50:57 +0100732}
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200733/*!
734 * Issue tracking description of several events: https://github.com/myriadrf/LimeSuite/issues/265
735 */
736void LMSDevice::update_stream_stats_rx(size_t chan, bool *overrun)
Pau Espin Pedrole5b66642018-12-04 20:56:32 +0100737{
738 lms_stream_status_t status;
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200739 bool changed = false;
Pau Espin Pedrole5b66642018-12-04 20:56:32 +0100740
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200741 if (LMS_GetStreamStatus(&m_lms_stream_rx[chan], &status) != 0) {
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200742 LOGCHAN(chan, DDEV, ERROR) << "Rx LMS_GetStreamStatus failed";
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200743 return;
Pau Espin Pedrole5b66642018-12-04 20:56:32 +0100744 }
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200745
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200746 /* FIFO overrun is counted when Rx FIFO is full but new data comes from
747 the board and oldest samples in FIFO are overwritte. Value count
748 since the last call to LMS_GetStreamStatus(stream). */
Pau Espin Pedrolbde55af2019-06-04 16:32:25 +0200749 if (status.overrun) {
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200750 changed = true;
751 *overrun = true;
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200752 LOGCHAN(chan, DDEV, ERROR) << "Rx Overrun! ("
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200753 << m_ctr[chan].rx_overruns << " -> "
754 << status.overrun << ")";
755 }
Pau Espin Pedrolbde55af2019-06-04 16:32:25 +0200756 m_ctr[chan].rx_overruns += status.overrun;
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200757
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200758 /* Dropped packets in Rx are counted when gaps in Rx timestamps are
Martin Hauke066fd042019-10-13 19:08:00 +0200759 detected (likely because buffer overflow in hardware). Value count
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200760 since the last call to LMS_GetStreamStatus(stream). */
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200761 if (status.droppedPackets) {
762 changed = true;
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200763 LOGCHAN(chan, DDEV, ERROR) << "Rx Dropped packets by HW! ("
Pau Espin Pedrol4456b6f2019-05-24 16:54:19 +0200764 << m_ctr[chan].rx_dropped_samples << " -> "
765 << m_ctr[chan].rx_dropped_samples +
766 status.droppedPackets
767 << ")";
768 m_ctr[chan].rx_dropped_events++;
769 }
770 m_ctr[chan].rx_dropped_samples += status.droppedPackets;
771
772 if (changed)
773 osmo_signal_dispatch(SS_DEVICE, S_DEVICE_COUNTER_CHANGE, &m_ctr[chan]);
774
Pau Espin Pedrole5b66642018-12-04 20:56:32 +0100775}
776
Harald Welte940738e2018-03-07 07:50:57 +0100777// NOTE: Assumes sequential reads
778int LMSDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun,
Pau Espin Pedrolf8c0c462020-03-12 19:08:46 +0100779 TIMESTAMP timestamp, bool * underrun)
Harald Welte940738e2018-03-07 07:50:57 +0100780{
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200781 int rc, num_smpls, expect_smpls;
782 ssize_t avail_smpls;
783 TIMESTAMP expect_timestamp;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200784 unsigned int i;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200785 lms_stream_meta_t rx_metadata = {};
786 rx_metadata.flushPartialPacket = false;
787 rx_metadata.waitForTimestamp = false;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200788 rx_metadata.timestamp = 0;
Harald Welte940738e2018-03-07 07:50:57 +0100789
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200790 if (bufs.size() != chans) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200791 LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size();
Harald Welte940738e2018-03-07 07:50:57 +0100792 return -1;
793 }
794
Harald Welte940738e2018-03-07 07:50:57 +0100795 *overrun = false;
796 *underrun = false;
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200797
798 /* Check that timestamp is valid */
799 rc = rx_buffers[0]->avail_smpls(timestamp);
800 if (rc < 0) {
801 LOGC(DDEV, ERROR) << rx_buffers[0]->str_code(rc);
802 LOGC(DDEV, ERROR) << rx_buffers[0]->str_status(timestamp);
803 return 0;
Harald Welte940738e2018-03-07 07:50:57 +0100804 }
805
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200806 for (i = 0; i<chans; i++) {
807 /* Receive samples from HW until we have enough */
808 while ((avail_smpls = rx_buffers[i]->avail_smpls(timestamp)) < len) {
809 thread_enable_cancel(false);
810 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 +0200811 update_stream_stats_rx(i, overrun);
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200812 thread_enable_cancel(true);
813 if (num_smpls <= 0) {
814 LOGCHAN(i, DDEV, ERROR) << "Device receive timed out (" << rc << " vs exp " << len << ").";
815 return -1;
816 }
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200817
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200818 LOGCHAN(i, DDEV, DEBUG) "Received timestamp = " << (TIMESTAMP)rx_metadata.timestamp << " (" << num_smpls << ")";
819
820 expect_smpls = len - avail_smpls;
821 if (expect_smpls != num_smpls)
822 LOGCHAN(i, DDEV, NOTICE) << "Unexpected recv buffer len: expect "
823 << expect_smpls << " got " << num_smpls
824 << ", diff=" << expect_smpls - num_smpls;
825
826 expect_timestamp = timestamp + avail_smpls;
827 if (expect_timestamp != (TIMESTAMP)rx_metadata.timestamp)
828 LOGCHAN(i, DDEV, ERROR) << "Unexpected recv buffer timestamp: expect "
829 << expect_timestamp << " got " << (TIMESTAMP)rx_metadata.timestamp
830 << ", diff=" << rx_metadata.timestamp - expect_timestamp;
831
832 rc = rx_buffers[i]->write(bufs[i], num_smpls, (TIMESTAMP)rx_metadata.timestamp);
833 if (rc < 0) {
834 LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_code(rc);
835 LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_status(timestamp);
836 if (rc != smpl_buf::ERROR_OVERFLOW)
837 return 0;
838 }
839 }
840 }
841
842 /* We have enough samples */
843 for (size_t i = 0; i < rx_buffers.size(); i++) {
844 rc = rx_buffers[i]->read(bufs[i], len, timestamp);
845 if ((rc < 0) || (rc != len)) {
Pau Espin Pedrol62c92802020-01-13 14:35:06 +0100846 LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_code(rc) << ". "
847 << rx_buffers[i]->str_status(timestamp)
848 << ", (len=" << len << ")";
Pau Espin Pedroldcbcfa52019-05-03 16:15:06 +0200849 return 0;
850 }
851 }
852
853 return len;
Harald Welte940738e2018-03-07 07:50:57 +0100854}
855
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200856void LMSDevice::update_stream_stats_tx(size_t chan, bool *underrun)
857{
858 lms_stream_status_t status;
859 bool changed = false;
860
861 if (LMS_GetStreamStatus(&m_lms_stream_tx[chan], &status) != 0) {
862 LOGCHAN(chan, DDEV, ERROR) << "Tx LMS_GetStreamStatus failed";
863 return;
864 }
865
866 /* FIFO underrun is counted when Tx is running but FIFO is empty for
867 >100 ms (500ms in older versions). Value count since the last call to
868 LMS_GetStreamStatus(stream). */
869 if (status.underrun) {
870 changed = true;
871 *underrun = true;
872 LOGCHAN(chan, DDEV, ERROR) << "Tx Underrun! ("
873 << m_ctr[chan].tx_underruns << " -> "
874 << status.underrun << ")";
875 }
876 m_ctr[chan].tx_underruns += status.underrun;
877
878 /* Dropped packets in Tx are counted only when timestamps are enabled
879 and SDR drops packet because of late timestamp. Value count since the
880 last call to LMS_GetStreamStatus(stream). */
881 if (status.droppedPackets) {
882 changed = true;
883 LOGCHAN(chan, DDEV, ERROR) << "Tx Dropped packets by HW! ("
884 << m_ctr[chan].tx_dropped_samples << " -> "
885 << m_ctr[chan].tx_dropped_samples +
886 status.droppedPackets
887 << ")";
888 m_ctr[chan].tx_dropped_events++;
889 }
890 m_ctr[chan].tx_dropped_samples += status.droppedPackets;
891
892 if (changed)
893 osmo_signal_dispatch(SS_DEVICE, S_DEVICE_COUNTER_CHANGE, &m_ctr[chan]);
894
895}
896
Harald Welte940738e2018-03-07 07:50:57 +0100897int LMSDevice::writeSamples(std::vector < short *>&bufs, int len,
Pau Espin Pedroldfc6e5f2020-03-12 19:35:33 +0100898 bool * underrun, unsigned long long timestamp)
Harald Welte940738e2018-03-07 07:50:57 +0100899{
Vadim Yanitskiy92fc1862018-09-20 16:17:16 +0700900 int rc = 0;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200901 unsigned int i;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200902 lms_stream_meta_t tx_metadata = {};
903 tx_metadata.flushPartialPacket = false;
904 tx_metadata.waitForTimestamp = true;
Zydrunas Tamosevicius43f36782018-06-12 00:27:09 +0200905 tx_metadata.timestamp = timestamp - ts_offset; /* Shift Tx time by offset */
Harald Welte940738e2018-03-07 07:50:57 +0100906
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200907 if (bufs.size() != chans) {
Pau Espin Pedrolb96d9dd2019-04-25 19:52:49 +0200908 LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size();
Harald Welte940738e2018-03-07 07:50:57 +0100909 return -1;
910 }
911
Harald Welte940738e2018-03-07 07:50:57 +0100912 *underrun = false;
913
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200914 for (i = 0; i<chans; i++) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200915 LOGCHAN(i, DDEV, DEBUG) << "send buffer of len " << len << " timestamp " << std::hex << tx_metadata.timestamp;
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200916 thread_enable_cancel(false);
917 rc = LMS_SendStream(&m_lms_stream_tx[i], bufs[i], len, &tx_metadata, 100);
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200918 update_stream_stats_tx(i, underrun);
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200919 thread_enable_cancel(true);
Pau Espin Pedrol68a78092019-07-29 20:11:25 +0200920 if (rc != len) {
921 LOGCHAN(i, DDEV, ERROR) << "LMS: Device Tx timed out (" << rc << " vs exp " << len << ").";
922 return -1;
923 }
Harald Welte940738e2018-03-07 07:50:57 +0100924 }
925
Harald Welte940738e2018-03-07 07:50:57 +0100926 return rc;
927}
928
929bool LMSDevice::updateAlignment(TIMESTAMP timestamp)
930{
Pau Espin Pedrolc7a0bf12018-04-25 12:17:10 +0200931 return true;
Harald Welte940738e2018-03-07 07:50:57 +0100932}
933
934bool LMSDevice::setTxFreq(double wFreq, size_t chan)
935{
Pau Espin Pedrolf68f19b2020-06-19 14:48:09 +0200936 if (chan >= chans) {
937 LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
938 return false;
939 }
940
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200941 LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz";
Pau Espin Pedrol138caaf2018-12-04 14:09:53 +0100942
Ericc0f78a32023-05-12 13:00:14 +0200943 if (!update_band_from_freq(wFreq, chan, true))
Ericecea7342021-07-11 21:12:04 +0200944 return false;
945
Harald Welte940738e2018-03-07 07:50:57 +0100946 if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200947 LOGCHAN(chan, DDEV, ERROR) << "Error setting Tx Freq to " << wFreq << " Hz";
Harald Welte940738e2018-03-07 07:50:57 +0100948 return false;
949 }
950
951 return true;
952}
953
954bool LMSDevice::setRxFreq(double wFreq, size_t chan)
955{
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200956 LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz";
Pau Espin Pedrol138caaf2018-12-04 14:09:53 +0100957
Ericc0f78a32023-05-12 13:00:14 +0200958 if (!update_band_from_freq(wFreq, chan, false))
Pau Espin Pedrol069f5cd2021-09-21 13:50:38 +0200959 return false;
960
Harald Welte940738e2018-03-07 07:50:57 +0100961 if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) {
Pau Espin Pedrolfc73c072019-05-03 19:40:00 +0200962 LOGCHAN(chan, DDEV, ERROR) << "Error setting Rx Freq to " << wFreq << " Hz";
Harald Welte940738e2018-03-07 07:50:57 +0100963 return false;
964 }
965
966 return true;
967}
968
Eric19e134a2023-05-10 23:50:38 +0200969RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg)
Harald Welte940738e2018-03-07 07:50:57 +0100970{
Eric19e134a2023-05-10 23:50:38 +0200971 if (cfg->tx_sps != cfg->rx_sps) {
972 LOGC(DDEV, ERROR) << "LMS requires tx_sps == rx_sps";
Harald Welteffb33012018-06-13 23:41:35 +0200973 return NULL;
974 }
Eric19e134a2023-05-10 23:50:38 +0200975 if (cfg->offset != 0.0) {
Harald Welte5cc88582018-08-17 19:55:38 +0200976 LOGC(DDEV, ERROR) << "LMS doesn't support lo_offset";
Harald Welteffb33012018-06-13 23:41:35 +0200977 return NULL;
978 }
Eric19e134a2023-05-10 23:50:38 +0200979 return new LMSDevice(type, cfg);
Harald Welte940738e2018-03-07 07:50:57 +0100980}