Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2018 sysmocom - s.f.m.c. GmbH |
| 3 | * |
Pau Espin Pedrol | 21d03d3 | 2019-07-22 12:05:52 +0200 | [diff] [blame] | 4 | * SPDX-License-Identifier: AGPL-3.0+ |
| 5 | * |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 6 | 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 Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 23 | |
| 24 | #include <map> |
| 25 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 26 | #include "Logger.h" |
| 27 | #include "Threads.h" |
| 28 | #include "LMSDevice.h" |
Oliver Smith | 871713b | 2018-12-10 17:10:36 +0100 | [diff] [blame] | 29 | #include "Utils.h" |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 30 | |
| 31 | #include <lime/LimeSuite.h> |
| 32 | |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 33 | extern "C" { |
Vadim Yanitskiy | a0d862b | 2020-10-24 19:18:13 +0700 | [diff] [blame] | 34 | #include "trx_vty.h" |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 35 | #include "osmo_signal.h" |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 36 | #include <osmocom/core/utils.h> |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 37 | } |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 38 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 39 | #ifdef HAVE_CONFIG_H |
| 40 | #include "config.h" |
| 41 | #endif |
| 42 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 43 | #define MAX_ANTENNA_LIST_SIZE 10 |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 44 | #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 Pedrol | dcbcfa5 | 2019-05-03 16:15:06 +0200 | [diff] [blame] | 47 | #define SAMPLE_BUF_SZ (1 << 20) /* Size of Rx timestamp based Ring buffer, in bytes */ |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 48 | |
Pau Espin Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 49 | |
| 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 Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 55 | |
Eric | c0f78a3 | 2023-05-12 13:00:14 +0200 | [diff] [blame^] | 56 | |
| 57 | static const dev_map_t dev_param_map { |
Pau Espin Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 58 | { 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 Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 62 | }; |
| 63 | |
Eric | c0f78a3 | 2023-05-12 13:00:14 +0200 | [diff] [blame^] | 64 | static const power_map_t dev_band_nom_power_param_map { |
Pau Espin Pedrol | e91544d | 2020-10-13 17:03:37 +0200 | [diff] [blame] | 65 | { 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 Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 77 | }; |
| 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 | */ |
| 82 | static 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 | } |
| 86 | static 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 Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 91 | static 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 | |
Eric | c0f78a3 | 2023-05-12 13:00:14 +0200 | [diff] [blame^] | 102 | 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 Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 104 | return dev_type; |
| 105 | } |
| 106 | it++; |
| 107 | } |
| 108 | return LMS_DEV_UNKNOWN; |
| 109 | } |
| 110 | |
Eric | 19e134a | 2023-05-10 23:50:38 +0200 | [diff] [blame] | 111 | LMSDevice::LMSDevice(InterfaceType iface, const struct trx_cfg *cfg) |
Eric | c0f78a3 | 2023-05-12 13:00:14 +0200 | [diff] [blame^] | 112 | : 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 Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 115 | { |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 116 | LOGC(DDEV, INFO) << "creating LMS device..."; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 117 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 118 | m_lms_stream_rx.resize(chans); |
| 119 | m_lms_stream_tx.resize(chans); |
Pau Espin Pedrol | 705a348 | 2019-09-13 16:51:48 +0200 | [diff] [blame] | 120 | rx_gains.resize(chans); |
| 121 | tx_gains.resize(chans); |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 122 | |
Pau Espin Pedrol | dcbcfa5 | 2019-05-03 16:15:06 +0200 | [diff] [blame] | 123 | rx_buffers.resize(chans); |
Pau Espin Pedrol | e7f6a27 | 2020-01-14 17:54:29 +0100 | [diff] [blame] | 124 | |
| 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 Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 129 | } |
| 130 | |
Pau Espin Pedrol | 5cea18e | 2018-12-03 18:17:18 +0100 | [diff] [blame] | 131 | LMSDevice::~LMSDevice() |
| 132 | { |
Joachim Steiger | 2d130fb | 2019-04-16 16:22:23 +0200 | [diff] [blame] | 133 | unsigned int i; |
Pau Espin Pedrol | 5cea18e | 2018-12-03 18:17:18 +0100 | [diff] [blame] | 134 | LOGC(DDEV, INFO) << "Closing LMS device"; |
| 135 | if (m_lms_dev) { |
Joachim Steiger | 2d130fb | 2019-04-16 16:22:23 +0200 | [diff] [blame] | 136 | /* 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 Pedrol | 5cea18e | 2018-12-03 18:17:18 +0100 | [diff] [blame] | 141 | LMS_Close(m_lms_dev); |
| 142 | m_lms_dev = NULL; |
| 143 | } |
Pau Espin Pedrol | dcbcfa5 | 2019-05-03 16:15:06 +0200 | [diff] [blame] | 144 | |
| 145 | for (size_t i = 0; i < rx_buffers.size(); i++) |
| 146 | delete rx_buffers[i]; |
Pau Espin Pedrol | 5cea18e | 2018-12-03 18:17:18 +0100 | [diff] [blame] | 147 | } |
| 148 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 149 | static void lms_log_callback(int lvl, const char *msg) |
| 150 | { |
| 151 | /* map lime specific log levels */ |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 152 | static const int lvl_map[5] = { |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 153 | [0] = LOGL_FATAL, |
Pau Espin Pedrol | 32b3c2e | 2018-11-23 14:39:06 +0100 | [diff] [blame] | 154 | [LMS_LOG_ERROR] = LOGL_ERROR, |
| 155 | [LMS_LOG_WARNING] = LOGL_NOTICE, |
| 156 | [LMS_LOG_INFO] = LOGL_INFO, |
| 157 | [LMS_LOG_DEBUG] = LOGL_DEBUG, |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 158 | }; |
| 159 | /* protect against future higher log level values (lower importance) */ |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 160 | if ((unsigned int) lvl >= ARRAY_SIZE(lvl_map)) |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 161 | lvl = ARRAY_SIZE(lvl_map)-1; |
| 162 | |
Pau Espin Pedrol | aebbfe0 | 2020-01-02 16:39:11 +0100 | [diff] [blame] | 163 | LOGLV(DDEVDRV, lvl_map[lvl]) << msg; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 164 | } |
| 165 | |
Pau Espin Pedrol | 6493dc8 | 2018-05-29 19:00:30 +0200 | [diff] [blame] | 166 | static void print_range(const char* name, lms_range_t *range) |
| 167 | { |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 168 | LOGC(DDEV, INFO) << name << ": Min=" << range->min << " Max=" << range->max |
Pau Espin Pedrol | 6493dc8 | 2018-05-29 19:00:30 +0200 | [diff] [blame] | 169 | << " Step=" << range->step; |
| 170 | } |
| 171 | |
Oliver Smith | 871713b | 2018-12-10 17:10:36 +0100 | [diff] [blame] | 172 | /*! 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) */ |
| 177 | int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::string &args) |
| 178 | { |
| 179 | unsigned int i, j; |
Vadim Yanitskiy | 6b4acc1 | 2020-10-24 07:05:22 +0700 | [diff] [blame] | 180 | std::vector<std::string> filters; |
Oliver Smith | 871713b | 2018-12-10 17:10:36 +0100 | [diff] [blame] | 181 | |
| 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 | |
Eric | 19e134a | 2023-05-10 23:50:38 +0200 | [diff] [blame] | 201 | int LMSDevice::open() |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 202 | { |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 203 | lms_info_str_t* info_list; |
Joachim Steiger | 4ce4555 | 2019-04-16 16:35:53 +0200 | [diff] [blame] | 204 | lms_range_t range_sr; |
| 205 | float_type sr_host, sr_rf; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 206 | unsigned int i, n; |
Oliver Smith | 871713b | 2018-12-10 17:10:36 +0100 | [diff] [blame] | 207 | int rc, dev_id; |
Pau Espin Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 208 | struct dev_desc dev_desc; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 209 | |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 210 | LOGC(DDEV, INFO) << "Opening LMS device.."; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 211 | |
| 212 | LMS_RegisterLogHandler(&lms_log_callback); |
| 213 | |
Vadim Yanitskiy | 019d698 | 2021-10-25 00:30:27 +0300 | [diff] [blame] | 214 | if ((rc = LMS_GetDeviceList(NULL)) < 0) |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 215 | LOGC(DDEV, ERROR) << "LMS_GetDeviceList(NULL) failed"; |
Vadim Yanitskiy | 019d698 | 2021-10-25 00:30:27 +0300 | [diff] [blame] | 216 | LOGC(DDEV, INFO) << "Devices found: " << rc; |
| 217 | if (rc < 1) |
Harald Welte | 62b7900 | 2018-06-13 23:32:42 +0200 | [diff] [blame] | 218 | return -1; |
Vadim Yanitskiy | 019d698 | 2021-10-25 00:30:27 +0300 | [diff] [blame] | 219 | n = rc; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 220 | |
Harald Welte | 62b7900 | 2018-06-13 23:32:42 +0200 | [diff] [blame] | 221 | info_list = new lms_info_str_t[n]; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 222 | |
Harald Welte | 62b7900 | 2018-06-13 23:32:42 +0200 | [diff] [blame] | 223 | if (LMS_GetDeviceList(info_list) < 0) |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 224 | LOGC(DDEV, ERROR) << "LMS_GetDeviceList(info_list) failed"; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 225 | |
Harald Welte | 62b7900 | 2018-06-13 23:32:42 +0200 | [diff] [blame] | 226 | for (i = 0; i < n; i++) |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 227 | LOGC(DDEV, INFO) << "Device [" << i << "]: " << info_list[i]; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 228 | |
Eric | 19e134a | 2023-05-10 23:50:38 +0200 | [diff] [blame] | 229 | dev_id = info_list_find(info_list, n, cfg->dev_args); |
Oliver Smith | 871713b | 2018-12-10 17:10:36 +0100 | [diff] [blame] | 230 | if (dev_id == -1) { |
Eric | 19e134a | 2023-05-10 23:50:38 +0200 | [diff] [blame] | 231 | LOGC(DDEV, ERROR) << "No LMS device found with address '" << cfg->dev_args << "'"; |
Oliver Smith | 871713b | 2018-12-10 17:10:36 +0100 | [diff] [blame] | 232 | 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 Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 238 | if (rc != 0) { |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 239 | LOGC(DDEV, ERROR) << "LMS_GetDeviceList() failed)"; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 240 | delete [] info_list; |
| 241 | return -1; |
| 242 | } |
| 243 | |
| 244 | delete [] info_list; |
| 245 | |
Pau Espin Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 246 | m_dev_type = parse_dev_type(m_lms_dev); |
| 247 | dev_desc = dev_param_map.at(m_dev_type); |
Joachim Steiger | 2875290 | 2019-04-16 17:10:13 +0200 | [diff] [blame] | 248 | |
Eric | 19e134a | 2023-05-10 23:50:38 +0200 | [diff] [blame] | 249 | if ((cfg->clock_ref != REF_EXTERNAL) && (cfg->clock_ref != REF_INTERNAL)) { |
Joachim Steiger | 2875290 | 2019-04-16 17:10:13 +0200 | [diff] [blame] | 250 | LOGC(DDEV, ERROR) << "Invalid reference type"; |
| 251 | goto out_close; |
| 252 | } |
| 253 | |
Pau Espin Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 254 | /* if reference clock is external, setup must happen _before_ calling LMS_Init */ |
Eric | 19e134a | 2023-05-10 23:50:38 +0200 | [diff] [blame] | 255 | if (cfg->clock_ref == REF_EXTERNAL) { |
Joachim Steiger | 2875290 | 2019-04-16 17:10:13 +0200 | [diff] [blame] | 256 | LOGC(DDEV, INFO) << "Setting External clock reference to 10MHz"; |
Pau Espin Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 257 | /* 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 Steiger | 2875290 | 2019-04-16 17:10:13 +0200 | [diff] [blame] | 260 | goto out_close; |
| 261 | } |
| 262 | |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 263 | LOGC(DDEV, INFO) << "Init LMS device"; |
Harald Welte | 9cb4f27 | 2018-04-28 21:38:58 +0200 | [diff] [blame] | 264 | if (LMS_Init(m_lms_dev) != 0) { |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 265 | LOGC(DDEV, ERROR) << "LMS_Init() failed"; |
Pau Espin Pedrol | ed361f9 | 2018-12-04 12:42:30 +0100 | [diff] [blame] | 266 | goto out_close; |
Harald Welte | 9cb4f27 | 2018-04-28 21:38:58 +0200 | [diff] [blame] | 267 | } |
| 268 | |
Pau Espin Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 269 | /* if reference clock is internal, setup must happen _after_ calling LMS_Init */ |
Eric | 19e134a | 2023-05-10 23:50:38 +0200 | [diff] [blame] | 270 | if (cfg->clock_ref == REF_INTERNAL) { |
Pau Espin Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 271 | 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 Steiger | 2875290 | 2019-04-16 17:10:13 +0200 | [diff] [blame] | 276 | |
Joachim Steiger | 2d130fb | 2019-04-16 16:22:23 +0200 | [diff] [blame] | 277 | /* 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 Steiger | c785fb1 | 2019-04-16 19:04:37 +0200 | [diff] [blame] | 285 | /* set samplerate */ |
Pau Espin Pedrol | 6493dc8 | 2018-05-29 19:00:30 +0200 | [diff] [blame] | 286 | if (LMS_GetSampleRateRange(m_lms_dev, LMS_CH_RX, &range_sr)) |
Harald Welte | a438114 | 2018-04-28 21:41:07 +0200 | [diff] [blame] | 287 | goto out_close; |
Pau Espin Pedrol | 6493dc8 | 2018-05-29 19:00:30 +0200 | [diff] [blame] | 288 | print_range("Sample Rate", &range_sr); |
Harald Welte | a438114 | 2018-04-28 21:41:07 +0200 | [diff] [blame] | 289 | |
Pau Espin Pedrol | 0569845 | 2020-01-14 19:02:40 +0100 | [diff] [blame] | 290 | 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 Pedrol | c69b87f | 2020-01-14 18:50:36 +0100 | [diff] [blame] | 294 | LOGC(DDEV, INFO) << "Setting sample rate to " << sr_host << " " << tx_sps; |
| 295 | if (LMS_SetSampleRate(m_lms_dev, sr_host, 32) < 0) |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 296 | goto out_close; |
Pau Espin Pedrol | 6493dc8 | 2018-05-29 19:00:30 +0200 | [diff] [blame] | 297 | |
Harald Welte | a438114 | 2018-04-28 21:41:07 +0200 | [diff] [blame] | 298 | if (LMS_GetSampleRate(m_lms_dev, LMS_CH_RX, 0, &sr_host, &sr_rf)) |
| 299 | goto out_close; |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 300 | LOGC(DDEV, INFO) << "Sample Rate: Host=" << sr_host << " RF=" << sr_rf; |
Pau Espin Pedrol | 6493dc8 | 2018-05-29 19:00:30 +0200 | [diff] [blame] | 301 | |
Pau Espin Pedrol | 0569845 | 2020-01-14 19:02:40 +0100 | [diff] [blame] | 302 | 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 Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 306 | |
Joachim Steiger | c785fb1 | 2019-04-16 19:04:37 +0200 | [diff] [blame] | 307 | /* configure antennas */ |
Harald Welte | cfb9dac | 2018-06-13 21:56:24 +0200 | [diff] [blame] | 308 | if (!set_antennas()) { |
Pau Espin Pedrol | b96d9dd | 2019-04-25 19:52:49 +0200 | [diff] [blame] | 309 | LOGC(DDEV, FATAL) << "LMS antenna setting failed"; |
Joachim Steiger | c785fb1 | 2019-04-16 19:04:37 +0200 | [diff] [blame] | 310 | goto out_close; |
Harald Welte | cfb9dac | 2018-06-13 21:56:24 +0200 | [diff] [blame] | 311 | } |
| 312 | |
Pau Espin Pedrol | 0569845 | 2020-01-14 19:02:40 +0100 | [diff] [blame] | 313 | return iface == MULTI_ARFCN ? MULTI_ARFCN : NORMAL; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 314 | |
| 315 | out_close: |
Pau Espin Pedrol | b96d9dd | 2019-04-25 19:52:49 +0200 | [diff] [blame] | 316 | LOGC(DDEV, FATAL) << "Error in LMS open, closing: " << LMS_GetLastErrorMessage(); |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 317 | LMS_Close(m_lms_dev); |
Pau Espin Pedrol | 5cea18e | 2018-12-03 18:17:18 +0100 | [diff] [blame] | 318 | m_lms_dev = NULL; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 319 | return -1; |
| 320 | } |
| 321 | |
| 322 | bool LMSDevice::start() |
| 323 | { |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 324 | LOGC(DDEV, INFO) << "starting LMS..."; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 325 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 326 | unsigned int i; |
Pau Espin Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 327 | dev_band_desc desc; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 328 | |
Pau Espin Pedrol | 69869bd | 2018-12-03 11:19:52 +0100 | [diff] [blame] | 329 | if (started) { |
| 330 | LOGC(DDEV, ERR) << "Device already started"; |
| 331 | return false; |
| 332 | } |
| 333 | |
Pau Espin Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 334 | get_dev_band_desc(desc); |
| 335 | |
Zydrunas Tamosevicius | 0494d05 | 2018-06-12 00:29:16 +0200 | [diff] [blame] | 336 | /* configure the channels/streams */ |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 337 | for (i=0; i<chans; i++) { |
Joachim Steiger | 2875290 | 2019-04-16 17:10:13 +0200 | [diff] [blame] | 338 | /* Set gains for calibration/filter setup */ |
| 339 | /* TX gain to maximum */ |
Pau Espin Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 340 | LMS_SetGaindB(m_lms_dev, LMS_CH_TX, i, TxPower2TxGain(desc, desc.nom_out_tx_power)); |
Joachim Steiger | 2875290 | 2019-04-16 17:10:13 +0200 | [diff] [blame] | 341 | /* RX gain to midpoint */ |
Harald Welte | 55928f2 | 2018-11-26 19:26:52 +0100 | [diff] [blame] | 342 | setRxGain((minRxGain() + maxRxGain()) / 2, i); |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 343 | |
Joachim Steiger | 4ce4555 | 2019-04-16 16:35:53 +0200 | [diff] [blame] | 344 | /* 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 Steiger | c785fb1 | 2019-04-16 19:04:37 +0200 | [diff] [blame] | 351 | /* configure Streams */ |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 352 | 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 Tamosevicius | 0494d05 | 2018-06-12 00:29:16 +0200 | [diff] [blame] | 371 | } |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 372 | |
Zydrunas Tamosevicius | 0494d05 | 2018-06-12 00:29:16 +0200 | [diff] [blame] | 373 | /* 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 Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 376 | 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 Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 381 | } |
| 382 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 383 | flush_recv(10); |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 384 | |
| 385 | started = true; |
| 386 | return true; |
| 387 | } |
| 388 | |
| 389 | bool LMSDevice::stop() |
| 390 | { |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 391 | unsigned int i; |
| 392 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 393 | if (!started) |
| 394 | return true; |
| 395 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 396 | for (i=0; i<chans; i++) { |
| 397 | LMS_StopStream(&m_lms_stream_tx[i]); |
| 398 | LMS_StopStream(&m_lms_stream_rx[i]); |
Pau Espin Pedrol | b4ea7b5 | 2018-12-03 11:34:23 +0100 | [diff] [blame] | 399 | } |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 400 | |
Pau Espin Pedrol | b4ea7b5 | 2018-12-03 11:34:23 +0100 | [diff] [blame] | 401 | 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 Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 404 | } |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 405 | |
Eric | c0f78a3 | 2023-05-12 13:00:14 +0200 | [diff] [blame^] | 406 | band_reset(); |
Pau Espin Pedrol | bb2cb9d | 2021-09-21 13:52:46 +0200 | [diff] [blame] | 407 | |
Pau Espin Pedrol | 69869bd | 2018-12-03 11:19:52 +0100 | [diff] [blame] | 408 | started = false; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 409 | return true; |
| 410 | } |
| 411 | |
Pau Espin Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 412 | bool 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) { |
Eric | c0f78a3 | 2023-05-12 13:00:14 +0200 | [diff] [blame^] | 423 | LOGC(DDEV, ERROR) |
| 424 | << "Device type " << dev_desc.desc_str << " doesn't support internal reference clock"; |
Pau Espin Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 425 | 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 { |
Eric | c0f78a3 | 2023-05-12 13:00:14 +0200 | [diff] [blame^] | 442 | LOGC(DDEV, INFO) |
| 443 | << "Device type " << dev_desc.desc_str << " doesn't support switching clock source through SW"; |
Pau Espin Pedrol | a7bf6cd | 2020-01-14 17:52:15 +0100 | [diff] [blame] | 444 | } |
| 445 | |
| 446 | return true; |
| 447 | } |
| 448 | |
Joachim Steiger | 4ce4555 | 2019-04-16 16:35:53 +0200 | [diff] [blame] | 449 | /* do rx/tx calibration - depends on gain, freq and bw */ |
| 450 | bool LMSDevice::do_calib(size_t chan) |
| 451 | { |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 452 | LOGCHAN(chan, DDEV, INFO) << "Calibrating"; |
Joachim Steiger | 4ce4555 | 2019-04-16 16:35:53 +0200 | [diff] [blame] | 453 | 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? */ |
| 461 | bool 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 Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 466 | LOGCHAN(chan, DDEV, INFO) << "Setting filters"; |
Joachim Steiger | 4ce4555 | 2019-04-16 16:35:53 +0200 | [diff] [blame] | 467 | 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 Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 477 | LOGCHAN(chan, DDEV, INFO) << "LPFBW: Rx=" << lpfbw_rx << " Tx=" << lpfbw_tx; |
Joachim Steiger | 4ce4555 | 2019-04-16 16:35:53 +0200 | [diff] [blame] | 478 | |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 479 | LOGCHAN(chan, DDEV, INFO) << "Setting LPFBW"; |
Joachim Steiger | 4ce4555 | 2019-04-16 16:35:53 +0200 | [diff] [blame] | 480 | 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 Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 487 | double LMSDevice::maxRxGain() |
| 488 | { |
Pau Espin Pedrol | 587916e | 2018-05-08 18:46:28 +0200 | [diff] [blame] | 489 | return 73.0; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 490 | } |
| 491 | |
| 492 | double LMSDevice::minRxGain() |
| 493 | { |
| 494 | return 0.0; |
| 495 | } |
| 496 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 497 | double LMSDevice::setRxGain(double dB, size_t chan) |
| 498 | { |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 499 | if (dB > maxRxGain()) |
| 500 | dB = maxRxGain(); |
| 501 | if (dB < minRxGain()) |
| 502 | dB = minRxGain(); |
| 503 | |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 504 | LOGCHAN(chan, DDEV, NOTICE) << "Setting RX gain to " << dB << " dB"; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 505 | |
| 506 | if (LMS_SetGaindB(m_lms_dev, LMS_CH_RX, chan, dB) < 0) |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 507 | LOGCHAN(chan, DDEV, ERR) << "Error setting RX gain to " << dB << " dB"; |
Pau Espin Pedrol | 705a348 | 2019-09-13 16:51:48 +0200 | [diff] [blame] | 508 | else |
| 509 | rx_gains[chan] = dB; |
| 510 | return rx_gains[chan]; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 511 | } |
| 512 | |
Pau Espin Pedrol | e91544d | 2020-10-13 17:03:37 +0200 | [diff] [blame] | 513 | double 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 Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 528 | double LMSDevice::setPowerAttenuation(int atten, size_t chan) |
| 529 | { |
Pau Espin Pedrol | 58d80a0 | 2020-06-19 19:29:59 +0200 | [diff] [blame] | 530 | double tx_power, dB; |
Pau Espin Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 531 | 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 Pedrol | 58d80a0 | 2020-06-19 19:29:59 +0200 | [diff] [blame] | 539 | tx_power = desc.nom_out_tx_power - atten; |
| 540 | dB = TxPower2TxGain(desc, tx_power); |
Pau Espin Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 541 | |
Pau Espin Pedrol | 58d80a0 | 2020-06-19 19:29:59 +0200 | [diff] [blame] | 542 | LOGCHAN(chan, DDEV, NOTICE) << "Setting TX gain to " << dB << " dB (~" << tx_power << " dBm)"; |
Pau Espin Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 543 | |
| 544 | if (LMS_SetGaindB(m_lms_dev, LMS_CH_TX, chan, dB) < 0) |
Pau Espin Pedrol | 58d80a0 | 2020-06-19 19:29:59 +0200 | [diff] [blame] | 545 | LOGCHAN(chan, DDEV, ERR) << "Error setting TX gain to " << dB << " dB (~" << tx_power << " dBm)"; |
Pau Espin Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 546 | else |
| 547 | tx_gains[chan] = dB; |
| 548 | return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]); |
| 549 | } |
| 550 | |
| 551 | double 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 Pedrol | 0e09e7c | 2020-05-29 16:39:07 +0200 | [diff] [blame] | 562 | int LMSDevice::getNominalTxPower(size_t chan) |
| 563 | { |
Pau Espin Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 564 | dev_band_desc desc; |
| 565 | get_dev_band_desc(desc); |
| 566 | |
| 567 | return desc.nom_out_tx_power; |
Pau Espin Pedrol | 0e09e7c | 2020-05-29 16:39:07 +0200 | [diff] [blame] | 568 | } |
| 569 | |
Pau Espin Pedrol | e0010fa | 2019-08-26 17:10:27 +0200 | [diff] [blame] | 570 | void 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 Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 584 | int LMSDevice::get_ant_idx(const std::string & name, bool dir_tx, size_t chan) |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 585 | { |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 586 | lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */ |
| 587 | const char* c_name = name.c_str(); |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 588 | int num_names; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 589 | int i; |
| 590 | |
| 591 | num_names = LMS_GetAntennaList(m_lms_dev, dir_tx, chan, name_list); |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 592 | for (i = 0; i < num_names; i++) { |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 593 | if (!strcmp(c_name, name_list[i])) |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 594 | return i; |
| 595 | } |
| 596 | return -1; |
| 597 | } |
| 598 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 599 | bool LMSDevice::flush_recv(size_t num_pkts) |
| 600 | { |
| 601 | #define CHUNK 625 |
Harald Welte | ce70ba5 | 2018-06-13 22:47:48 +0200 | [diff] [blame] | 602 | int len = CHUNK * tx_sps; |
Pau Espin Pedrol | 541496b | 2019-04-25 19:22:07 +0200 | [diff] [blame] | 603 | short *buffer = (short*) alloca(sizeof(short) * len * 2); |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 604 | 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 Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 613 | LOGC(DDEV, DEBUG) << "Flush: Recv buffer of len " << rc << " at " << std::hex << rx_metadata.timestamp; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 614 | if (rc != len) { |
Pau Espin Pedrol | b96d9dd | 2019-04-25 19:52:49 +0200 | [diff] [blame] | 615 | LOGC(DDEV, ERROR) << "Flush: Device receive timed out"; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 616 | return false; |
| 617 | } |
| 618 | |
Harald Welte | 68f0541 | 2018-04-28 21:58:03 +0200 | [diff] [blame] | 619 | ts_initial = rx_metadata.timestamp + len; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 620 | } |
| 621 | |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 622 | LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 623 | return true; |
| 624 | } |
| 625 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 626 | bool LMSDevice::setRxAntenna(const std::string & ant, size_t chan) |
| 627 | { |
| 628 | int idx; |
| 629 | |
| 630 | if (chan >= rx_paths.size()) { |
Pau Espin Pedrol | b96d9dd | 2019-04-25 19:52:49 +0200 | [diff] [blame] | 631 | LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 632 | return false; |
| 633 | } |
| 634 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 635 | idx = get_ant_idx(ant, LMS_CH_RX, chan); |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 636 | if (idx < 0) { |
Pau Espin Pedrol | e0010fa | 2019-08-26 17:10:27 +0200 | [diff] [blame] | 637 | 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 Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 641 | return false; |
| 642 | } |
| 643 | |
| 644 | if (LMS_SetAntenna(m_lms_dev, LMS_CH_RX, chan, idx) < 0) { |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 645 | LOGCHAN(chan, DDEV, ERROR) << "Unable to set Rx Antenna"; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 646 | } |
| 647 | |
| 648 | return true; |
| 649 | } |
| 650 | |
| 651 | std::string LMSDevice::getRxAntenna(size_t chan) |
| 652 | { |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 653 | lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */ |
| 654 | int idx; |
| 655 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 656 | if (chan >= rx_paths.size()) { |
Pau Espin Pedrol | b96d9dd | 2019-04-25 19:52:49 +0200 | [diff] [blame] | 657 | LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 658 | return ""; |
| 659 | } |
| 660 | |
| 661 | idx = LMS_GetAntenna(m_lms_dev, LMS_CH_RX, chan); |
| 662 | if (idx < 0) { |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 663 | LOGCHAN(chan, DDEV, ERROR) << "Error getting Rx Antenna"; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 664 | return ""; |
| 665 | } |
| 666 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 667 | if (LMS_GetAntennaList(m_lms_dev, LMS_CH_RX, chan, name_list) < idx) { |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 668 | LOGCHAN(chan, DDEV, ERROR) << "Error getting Rx Antenna List"; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 669 | return ""; |
| 670 | } |
| 671 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 672 | return name_list[idx]; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 673 | } |
| 674 | |
| 675 | bool LMSDevice::setTxAntenna(const std::string & ant, size_t chan) |
| 676 | { |
| 677 | int idx; |
| 678 | |
| 679 | if (chan >= tx_paths.size()) { |
Pau Espin Pedrol | b96d9dd | 2019-04-25 19:52:49 +0200 | [diff] [blame] | 680 | LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 681 | return false; |
| 682 | } |
| 683 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 684 | idx = get_ant_idx(ant, LMS_CH_TX, chan); |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 685 | if (idx < 0) { |
Pau Espin Pedrol | e0010fa | 2019-08-26 17:10:27 +0200 | [diff] [blame] | 686 | 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 Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 690 | return false; |
| 691 | } |
| 692 | |
| 693 | if (LMS_SetAntenna(m_lms_dev, LMS_CH_TX, chan, idx) < 0) { |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 694 | LOGCHAN(chan, DDEV, ERROR) << "Unable to set Rx Antenna"; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 695 | } |
| 696 | |
| 697 | return true; |
| 698 | } |
| 699 | |
| 700 | std::string LMSDevice::getTxAntenna(size_t chan) |
| 701 | { |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 702 | lms_name_t name_list[MAX_ANTENNA_LIST_SIZE]; /* large enough list for antenna names. */ |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 703 | int idx; |
| 704 | |
| 705 | if (chan >= tx_paths.size()) { |
Pau Espin Pedrol | b96d9dd | 2019-04-25 19:52:49 +0200 | [diff] [blame] | 706 | LOGC(DDEV, ERROR) << "Requested non-existent channel " << chan; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 707 | return ""; |
| 708 | } |
| 709 | |
| 710 | idx = LMS_GetAntenna(m_lms_dev, LMS_CH_TX, chan); |
| 711 | if (idx < 0) { |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 712 | LOGCHAN(chan, DDEV, ERROR) << "Error getting Tx Antenna"; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 713 | return ""; |
| 714 | } |
| 715 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 716 | if (LMS_GetAntennaList(m_lms_dev, LMS_CH_TX, chan, name_list) < idx) { |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 717 | LOGCHAN(chan, DDEV, ERROR) << "Error getting Tx Antenna List"; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 718 | return ""; |
| 719 | } |
| 720 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 721 | return name_list[idx]; |
| 722 | } |
| 723 | |
| 724 | bool LMSDevice::requiresRadioAlign() |
| 725 | { |
| 726 | return false; |
| 727 | } |
| 728 | |
| 729 | GSM::Time LMSDevice::minLatency() { |
Joachim Steiger | 6c5f4ba | 2019-04-16 16:13:02 +0200 | [diff] [blame] | 730 | /* UNUSED on limesdr (only used on usrp1/2) */ |
| 731 | return GSM::Time(0,0); |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 732 | } |
Pau Espin Pedrol | 68a7809 | 2019-07-29 20:11:25 +0200 | [diff] [blame] | 733 | /*! |
| 734 | * Issue tracking description of several events: https://github.com/myriadrf/LimeSuite/issues/265 |
| 735 | */ |
| 736 | void LMSDevice::update_stream_stats_rx(size_t chan, bool *overrun) |
Pau Espin Pedrol | e5b6664 | 2018-12-04 20:56:32 +0100 | [diff] [blame] | 737 | { |
| 738 | lms_stream_status_t status; |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 739 | bool changed = false; |
Pau Espin Pedrol | e5b6664 | 2018-12-04 20:56:32 +0100 | [diff] [blame] | 740 | |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 741 | if (LMS_GetStreamStatus(&m_lms_stream_rx[chan], &status) != 0) { |
Pau Espin Pedrol | 68a7809 | 2019-07-29 20:11:25 +0200 | [diff] [blame] | 742 | LOGCHAN(chan, DDEV, ERROR) << "Rx LMS_GetStreamStatus failed"; |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 743 | return; |
Pau Espin Pedrol | e5b6664 | 2018-12-04 20:56:32 +0100 | [diff] [blame] | 744 | } |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 745 | |
Pau Espin Pedrol | 68a7809 | 2019-07-29 20:11:25 +0200 | [diff] [blame] | 746 | /* 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 Pedrol | bde55af | 2019-06-04 16:32:25 +0200 | [diff] [blame] | 749 | if (status.overrun) { |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 750 | changed = true; |
| 751 | *overrun = true; |
Pau Espin Pedrol | 68a7809 | 2019-07-29 20:11:25 +0200 | [diff] [blame] | 752 | LOGCHAN(chan, DDEV, ERROR) << "Rx Overrun! (" |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 753 | << m_ctr[chan].rx_overruns << " -> " |
| 754 | << status.overrun << ")"; |
| 755 | } |
Pau Espin Pedrol | bde55af | 2019-06-04 16:32:25 +0200 | [diff] [blame] | 756 | m_ctr[chan].rx_overruns += status.overrun; |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 757 | |
Pau Espin Pedrol | 68a7809 | 2019-07-29 20:11:25 +0200 | [diff] [blame] | 758 | /* Dropped packets in Rx are counted when gaps in Rx timestamps are |
Martin Hauke | 066fd04 | 2019-10-13 19:08:00 +0200 | [diff] [blame] | 759 | detected (likely because buffer overflow in hardware). Value count |
Pau Espin Pedrol | 68a7809 | 2019-07-29 20:11:25 +0200 | [diff] [blame] | 760 | since the last call to LMS_GetStreamStatus(stream). */ |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 761 | if (status.droppedPackets) { |
| 762 | changed = true; |
Pau Espin Pedrol | 68a7809 | 2019-07-29 20:11:25 +0200 | [diff] [blame] | 763 | LOGCHAN(chan, DDEV, ERROR) << "Rx Dropped packets by HW! (" |
Pau Espin Pedrol | 4456b6f | 2019-05-24 16:54:19 +0200 | [diff] [blame] | 764 | << 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 Pedrol | e5b6664 | 2018-12-04 20:56:32 +0100 | [diff] [blame] | 775 | } |
| 776 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 777 | // NOTE: Assumes sequential reads |
| 778 | int LMSDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun, |
Pau Espin Pedrol | f8c0c46 | 2020-03-12 19:08:46 +0100 | [diff] [blame] | 779 | TIMESTAMP timestamp, bool * underrun) |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 780 | { |
Pau Espin Pedrol | dcbcfa5 | 2019-05-03 16:15:06 +0200 | [diff] [blame] | 781 | int rc, num_smpls, expect_smpls; |
| 782 | ssize_t avail_smpls; |
| 783 | TIMESTAMP expect_timestamp; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 784 | unsigned int i; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 785 | lms_stream_meta_t rx_metadata = {}; |
| 786 | rx_metadata.flushPartialPacket = false; |
| 787 | rx_metadata.waitForTimestamp = false; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 788 | rx_metadata.timestamp = 0; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 789 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 790 | if (bufs.size() != chans) { |
Pau Espin Pedrol | b96d9dd | 2019-04-25 19:52:49 +0200 | [diff] [blame] | 791 | LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size(); |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 792 | return -1; |
| 793 | } |
| 794 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 795 | *overrun = false; |
| 796 | *underrun = false; |
Pau Espin Pedrol | dcbcfa5 | 2019-05-03 16:15:06 +0200 | [diff] [blame] | 797 | |
| 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 Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 804 | } |
| 805 | |
Pau Espin Pedrol | dcbcfa5 | 2019-05-03 16:15:06 +0200 | [diff] [blame] | 806 | 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 Pedrol | 68a7809 | 2019-07-29 20:11:25 +0200 | [diff] [blame] | 811 | update_stream_stats_rx(i, overrun); |
Pau Espin Pedrol | dcbcfa5 | 2019-05-03 16:15:06 +0200 | [diff] [blame] | 812 | 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 Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 817 | |
Pau Espin Pedrol | dcbcfa5 | 2019-05-03 16:15:06 +0200 | [diff] [blame] | 818 | 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 Pedrol | 62c9280 | 2020-01-13 14:35:06 +0100 | [diff] [blame] | 846 | LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_code(rc) << ". " |
| 847 | << rx_buffers[i]->str_status(timestamp) |
| 848 | << ", (len=" << len << ")"; |
Pau Espin Pedrol | dcbcfa5 | 2019-05-03 16:15:06 +0200 | [diff] [blame] | 849 | return 0; |
| 850 | } |
| 851 | } |
| 852 | |
| 853 | return len; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 854 | } |
| 855 | |
Pau Espin Pedrol | 68a7809 | 2019-07-29 20:11:25 +0200 | [diff] [blame] | 856 | void 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 Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 897 | int LMSDevice::writeSamples(std::vector < short *>&bufs, int len, |
Pau Espin Pedrol | dfc6e5f | 2020-03-12 19:35:33 +0100 | [diff] [blame] | 898 | bool * underrun, unsigned long long timestamp) |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 899 | { |
Vadim Yanitskiy | 92fc186 | 2018-09-20 16:17:16 +0700 | [diff] [blame] | 900 | int rc = 0; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 901 | unsigned int i; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 902 | lms_stream_meta_t tx_metadata = {}; |
| 903 | tx_metadata.flushPartialPacket = false; |
| 904 | tx_metadata.waitForTimestamp = true; |
Zydrunas Tamosevicius | 43f3678 | 2018-06-12 00:27:09 +0200 | [diff] [blame] | 905 | tx_metadata.timestamp = timestamp - ts_offset; /* Shift Tx time by offset */ |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 906 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 907 | if (bufs.size() != chans) { |
Pau Espin Pedrol | b96d9dd | 2019-04-25 19:52:49 +0200 | [diff] [blame] | 908 | LOGC(DDEV, ERROR) << "Invalid channel combination " << bufs.size(); |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 909 | return -1; |
| 910 | } |
| 911 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 912 | *underrun = false; |
| 913 | |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 914 | for (i = 0; i<chans; i++) { |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 915 | LOGCHAN(i, DDEV, DEBUG) << "send buffer of len " << len << " timestamp " << std::hex << tx_metadata.timestamp; |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 916 | thread_enable_cancel(false); |
| 917 | rc = LMS_SendStream(&m_lms_stream_tx[i], bufs[i], len, &tx_metadata, 100); |
Pau Espin Pedrol | 68a7809 | 2019-07-29 20:11:25 +0200 | [diff] [blame] | 918 | update_stream_stats_tx(i, underrun); |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 919 | thread_enable_cancel(true); |
Pau Espin Pedrol | 68a7809 | 2019-07-29 20:11:25 +0200 | [diff] [blame] | 920 | if (rc != len) { |
| 921 | LOGCHAN(i, DDEV, ERROR) << "LMS: Device Tx timed out (" << rc << " vs exp " << len << ")."; |
| 922 | return -1; |
| 923 | } |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 924 | } |
| 925 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 926 | return rc; |
| 927 | } |
| 928 | |
| 929 | bool LMSDevice::updateAlignment(TIMESTAMP timestamp) |
| 930 | { |
Pau Espin Pedrol | c7a0bf1 | 2018-04-25 12:17:10 +0200 | [diff] [blame] | 931 | return true; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 932 | } |
| 933 | |
| 934 | bool LMSDevice::setTxFreq(double wFreq, size_t chan) |
| 935 | { |
Pau Espin Pedrol | f68f19b | 2020-06-19 14:48:09 +0200 | [diff] [blame] | 936 | if (chan >= chans) { |
| 937 | LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; |
| 938 | return false; |
| 939 | } |
| 940 | |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 941 | LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz"; |
Pau Espin Pedrol | 138caaf | 2018-12-04 14:09:53 +0100 | [diff] [blame] | 942 | |
Eric | c0f78a3 | 2023-05-12 13:00:14 +0200 | [diff] [blame^] | 943 | if (!update_band_from_freq(wFreq, chan, true)) |
Eric | ecea734 | 2021-07-11 21:12:04 +0200 | [diff] [blame] | 944 | return false; |
| 945 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 946 | if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) { |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 947 | LOGCHAN(chan, DDEV, ERROR) << "Error setting Tx Freq to " << wFreq << " Hz"; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 948 | return false; |
| 949 | } |
| 950 | |
| 951 | return true; |
| 952 | } |
| 953 | |
| 954 | bool LMSDevice::setRxFreq(double wFreq, size_t chan) |
| 955 | { |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 956 | LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz"; |
Pau Espin Pedrol | 138caaf | 2018-12-04 14:09:53 +0100 | [diff] [blame] | 957 | |
Eric | c0f78a3 | 2023-05-12 13:00:14 +0200 | [diff] [blame^] | 958 | if (!update_band_from_freq(wFreq, chan, false)) |
Pau Espin Pedrol | 069f5cd | 2021-09-21 13:50:38 +0200 | [diff] [blame] | 959 | return false; |
| 960 | |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 961 | if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) { |
Pau Espin Pedrol | fc73c07 | 2019-05-03 19:40:00 +0200 | [diff] [blame] | 962 | LOGCHAN(chan, DDEV, ERROR) << "Error setting Rx Freq to " << wFreq << " Hz"; |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 963 | return false; |
| 964 | } |
| 965 | |
| 966 | return true; |
| 967 | } |
| 968 | |
Eric | 19e134a | 2023-05-10 23:50:38 +0200 | [diff] [blame] | 969 | RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 970 | { |
Eric | 19e134a | 2023-05-10 23:50:38 +0200 | [diff] [blame] | 971 | if (cfg->tx_sps != cfg->rx_sps) { |
| 972 | LOGC(DDEV, ERROR) << "LMS requires tx_sps == rx_sps"; |
Harald Welte | ffb3301 | 2018-06-13 23:41:35 +0200 | [diff] [blame] | 973 | return NULL; |
| 974 | } |
Eric | 19e134a | 2023-05-10 23:50:38 +0200 | [diff] [blame] | 975 | if (cfg->offset != 0.0) { |
Harald Welte | 5cc8858 | 2018-08-17 19:55:38 +0200 | [diff] [blame] | 976 | LOGC(DDEV, ERROR) << "LMS doesn't support lo_offset"; |
Harald Welte | ffb3301 | 2018-06-13 23:41:35 +0200 | [diff] [blame] | 977 | return NULL; |
| 978 | } |
Eric | 19e134a | 2023-05-10 23:50:38 +0200 | [diff] [blame] | 979 | return new LMSDevice(type, cfg); |
Harald Welte | 940738e | 2018-03-07 07:50:57 +0100 | [diff] [blame] | 980 | } |