yate/modules/radio/ybladerf.cpp

11603 lines
377 KiB
C++

/**
* ybladerf.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* BladeRF radio interface
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2015, 2016 Null Team
* Copyright (C) 2015, 2016 LEGBA Inc
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <yatephone.h>
#include <yateradio.h>
#include <yatemath.h>
#include <libusb-1.0/libusb.h>
#ifdef __MMX__
#include <mmintrin.h>
#endif
#ifndef M_PI_2
#define M_PI_2 (M_PI / 2)
#endif
#ifndef M_PI_4
#define M_PI_4 (M_PI / 4)
#endif
#ifdef LITTLE_ENDIAN
#define BRF_LITTLE_ENDIAN (true)
#else
#define BRF_LITTLE_ENDIAN (false)
#endif
#define BRF_MAX_FLOAT ((float)0xffffffff)
//#define DEBUG_LUSB_TRANSFER_CALLBACK // Debug libusb transfer callback
//#define DEBUGGER_DEVICE_METH // Instantiate a Debugger to trace some device methods
//#define DEBUG_DEVICE_TX // Special (verbose) TX debug
//#define DEBUG_DEVICE_RX // Special (verbose) RX debug
//#define DEBUG_DEVICE_AUTOCAL // Special (verbose) autocalibration debug
using namespace TelEngine;
namespace { // anonymous
class BrfLibUsbDevice; // A bladeRF device using libusb
class BrfInterface; // A bladeRF radio interface
class BrfModule; // The module
// GPIO
// Configure FPGA to send smaller buffers (USB 2)
#define BRF_GPIO_SMALL_DMA_XFER (1 << 7)
// SPI flash page size, in bytes
#define BRF_FLASH_PAGE_SIZE 256
#define SI5338_F_VCO (38400000UL * 66UL)
// Vendor commands
#define BRF_USB_CMD_QUERY_FPGA_STATUS 1
#define BRF_USB_CMD_BEGIN_PROG 2
#define BRF_USB_CMD_RF_RX 4
#define BRF_USB_CMD_RF_TX 5
#define BRF_USB_CMD_READ_CAL_CACHE 110
#define BRF_SAMPLERATE_MIN 80000u
#define BRF_SAMPLERATE_MAX 40000000 // Max supported by LMS6002D
#define MAX_SAMPLERATE_HIGH 4100000
#define MAX_SAMPLERATE_SUPER 40000000
// Frequency bounds
#define BRF_FREQUENCY_MIN 232500000u
#define BRF_FREQUENCY_MAX 3800000000u
// Frequency offset interval
#define BRF_FREQ_OFFS_DEF 128.0
#define BRF_FREQ_OFFS_MIN 64.0
#define BRF_FREQ_OFFS_MAX 192.0
#define BRF_MAX_DELAY_SUPER_SPEED_DEF 550
#define BRF_MAX_DELAY_HIGH_SPEED_DEF 750
#define BRF_BEST_DELAY_SUPER_SPEED_DEF 450
#define BRF_BEST_DELAY_HIGH_SPEED_DEF 600
#define BRF_KNOWN_DELAY_SUPER_SPEED_DEF 400
#define BRF_KNOWN_DELAY_HIGH_SPEED_DEF 500
#define BRF_SYSTEM_ACCURACY_DEF 300
#define BRF_ACCURACY_PPB_DEF 30
#define BRF_RXVGA1_GAIN_MIN 5
#define BRF_RXVGA1_GAIN_MAX 30
#define BRF_RXVGA1_GAIN_DEF 30
#define BRF_RXVGA2_GAIN_MIN 0
#define BRF_RXVGA2_GAIN_MAX 30
#define BRF_RXVGA2_GAIN_DEF 3
#define BRF_TXVGA1_GAIN_MIN -35
#define BRF_TXVGA1_GAIN_MAX -4
#define BRF_TXVGA1_GAIN_DEF -14
#define BRF_TXVGA2_GAIN_MIN 0
#define BRF_TXVGA2_GAIN_MAX 25
#define BRF_TXVGA2_GAIN_DEF 0
#define VCO_HIGH 0x02
#define VCO_NORM 0x00
#define VCO_LOW 0x01
#define MAKE_TOKEN_NAME_PREFIX(PREFIX,x) {#x, PREFIX##x}
static inline bool brfIsLowBand(unsigned int hz)
{
return hz < 1500000000u;
}
#define BRF_ALTSET_INVALID -1 // Invalid value
#define BRF_ALTSET_IDLE 0 // Used for idle mode
#define BRF_ALTSET_RF_LINK 1 // Send and receive samples. Also used for
// sending commands to peripherals (VCTCXO, Si5338, LMS6002D)
#define BRF_ALTSET_SPI_FLASH 2 // Update the firmware on the board
#define BRF_ALTSET_FPGA 3 // FPGA operations
#define BRF_ALTSET_MIN BRF_ALTSET_IDLE
#define BRF_ALTSET_MAX BRF_ALTSET_FPGA
static const TokenDict s_altSetDict[] = {
MAKE_TOKEN_NAME_PREFIX(BRF_ALTSET_,INVALID),
MAKE_TOKEN_NAME_PREFIX(BRF_ALTSET_,IDLE),
MAKE_TOKEN_NAME_PREFIX(BRF_ALTSET_,RF_LINK),
MAKE_TOKEN_NAME_PREFIX(BRF_ALTSET_,SPI_FLASH),
MAKE_TOKEN_NAME_PREFIX(BRF_ALTSET_,FPGA),
{0,0}
};
static inline const char* altSetName(int val)
{
return lookup(val,s_altSetDict);
}
// USB endpoints
#define BRF_ENDP_TX_SAMPLES 0x01 // Endpoint for TX data samples
#define BRF_ENDP_TX_CTRL 0x02 // Endpoint for ctrl RF
#define BRF_ENDP_RX_SAMPLES 0x81 // Endpoint for RX data samples
#define BRF_ENDP_RX_CTRL 0x82 // Endpoint for ctrl RF
// DC calibrate modules
// Auto calibration order is given in LMS6002D calibration guide, Section 4.7
#define BRF_CALIBRATE_LPF_TUNING 0 // DC offset cancellation of LPF
#define BRF_CALIBRATE_LPF_BANDWIDTH 1 // LPF bandwidth tunning
#define BRF_CALIBRATE_TX_LPF 2 // DC offset cancellation of TX LPF
#define BRF_CALIBRATE_RX_LPF 3 // DC offset cancellation of RX LPF
#define BRF_CALIBRATE_RX_VGA2 4 // DC offset cancellation of RX VGA2
#define BRF_CALIBRATE_FIRST BRF_CALIBRATE_LPF_TUNING
#define BRF_CALIBRATE_LAST BRF_CALIBRATE_RX_VGA2
#define BRF_CALIBRATE_MAX_SUBMODULES 5
static const TokenDict s_calModuleDict[] = {
MAKE_TOKEN_NAME_PREFIX(BRF_CALIBRATE_,LPF_TUNING),
MAKE_TOKEN_NAME_PREFIX(BRF_CALIBRATE_,LPF_BANDWIDTH),
MAKE_TOKEN_NAME_PREFIX(BRF_CALIBRATE_,TX_LPF),
MAKE_TOKEN_NAME_PREFIX(BRF_CALIBRATE_,RX_LPF),
MAKE_TOKEN_NAME_PREFIX(BRF_CALIBRATE_,RX_VGA2),
{0,0}
};
static inline const char* calModName(int val)
{
return lookup(val,s_calModuleDict);
}
static const char* s_calRxTxLpfNames[] = {"DC_I","DC_Q"};
static const char* s_calRxVga2Names[] = {"VGA2_DC_REF","VGA2A_DC_I","VGA2A_DC_Q","VGA2B_DC_I","VGA2B_DC_Q"};
struct BrfCalDesc
{
uint8_t clkEnMask;
uint8_t addr;
uint8_t subModules;
const char** subModName;
};
static const BrfCalDesc s_calModuleDesc[] = {
// BRF_CALIBRATE_LPF_TUNING
{0x20,0x00,1,0},
// BRF_CALIBRATE_LPF_BANDWIDTH
{0,0,1,0},
// BRF_CALIBRATE_TX_LPF
{0x02,0x30,2,s_calRxTxLpfNames},
// BRF_CALIBRATE_RX_LPF
{0x08,0x50,2,s_calRxTxLpfNames},
// BRF_CALIBRATE_RX_VGA2
{0x10,0x60,5,s_calRxVga2Names}
};
// Maximum values for Rx/Tx DC offset I and Q
#define BRF_RX_DC_OFFSET_MAX 63
#define BRF_TX_DC_OFFSET_MIN -128
#define BRF_TX_DC_OFFSET_MAX 127
static inline int16_t decodeDCOffs(bool tx, uint8_t val)
{
if (tx) {
bool negative = ((val & 0x80) == 0);
return negative ? ((int16_t)val - 128) : (int16_t)(val & 0x7f);
}
bool negative = ((val & 0x40) != 0);
return negative ? -(int16_t)(val & 0x3f) : (int16_t)(val & 0x3f);
}
// Calculate Rx DC offset correction
#define BRF_RX_DC_OFFSET_ERROR 10
#define BRF_RX_DC_OFFSET_COEF 1.5
#define BRF_RX_DC_OFFSET_AVG_DAMPING 1024
#define BRF_RX_DC_OFFSET_DEF (BRF_RX_DC_OFFSET_ERROR * BRF_RX_DC_OFFSET_AVG_DAMPING)
static inline double brfRxDcOffset(double val)
{
return (val * BRF_RX_DC_OFFSET_COEF + BRF_RX_DC_OFFSET_ERROR) *
BRF_RX_DC_OFFSET_AVG_DAMPING;
}
// FPGA correction
#define BRF_FPGA_CORR_MAX 4096
// libusb defaults
#define LUSB_SYNC_TIMEOUT 50 // Sync transfer timeout def val (in milliseconds)
#define LUSB_CTRL_TIMEOUT 500 // Control transfer timeout def val (in milliseconds)
#define LUSB_BULK_TIMEOUT 500 // Bulk transfer timeout def val (in milliseconds)
// libusb control transfer
#define LUSB_CTRLTRANS_IFACE_VENDOR (LIBUSB_RECIPIENT_INTERFACE | LIBUSB_REQUEST_TYPE_VENDOR)
#define LUSB_CTRLTRANS_IFACE_VENDOR_IN (LUSB_CTRLTRANS_IFACE_VENDOR | LIBUSB_ENDPOINT_IN)
#define LUSB_CTRLTRANS_DEV_VENDOR (LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_VENDOR)
#define LUSB_CTRLTRANS_DEV_VENDOR_IN (LUSB_CTRLTRANS_DEV_VENDOR | LIBUSB_ENDPOINT_IN)
#define LUSB_CTRLTRANS_DEV_VENDOR_OUT (LUSB_CTRLTRANS_DEV_VENDOR | LIBUSB_ENDPOINT_OUT)
// Board reference clock (in Hz)
static const uint64_t s_freqRefClock = 38400000;
static inline unsigned int bytes2samplesf(unsigned int bytes)
{
return bytes / (2 * sizeof(float));
}
static inline unsigned int samplesf2bytes(unsigned int samples)
{
return samples * 2 * sizeof(float);
}
static inline unsigned int samplesi2bytes(unsigned int samples)
{
return samples * 2 * sizeof(int16_t);
}
static inline const char* dirStr(int8_t dir)
{
return dir ? (dir > 0 ? "u" : "d") : "=";
}
static inline const char* encloseDashes(String& s, bool extra = false)
{
static const String s1 = "\r\n-----";
if (s)
s = s1 + (extra ? "\r\n" : "") + s + s1;
return s.safe();
}
#define BRF_ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
// Utility: check timeout or cancelled
static inline unsigned int checkCancelled(String* error = 0)
{
if (!Thread::check(false))
return 0;
if (error)
*error = "Cancelled";
return RadioInterface::Cancelled;
}
static inline float getSampleLimit(const NamedList& p, double defVal = (double)2040 / 2047)
{
float limit = (float)p.getDoubleValue(YSTRING("sample_limit"),defVal);
return (limit < 0) ? -limit : ((limit <= 1.0F) ? limit : 1.0F);
}
static inline const char* onStr(bool on)
{
return on ? "on" : "off";
}
static inline const char* enableStr(bool on)
{
return on ? "enable" : "disable";
}
static inline const char* enabledStr(bool on)
{
return on ? "Enabled" : "Disabled";
}
static inline const char* brfDir(bool tx)
{
return tx ? "TX" : "RX";
}
static inline char mixer(bool pre)
{
return pre ? '1' : '2';
}
static inline char brfIQ(bool i)
{
return i ? 'I' : 'Q';
}
static inline const char* activeStr(bool on)
{
return on ? "active" : "inactive";
}
static inline String& dumpFloatG(String& buf, double val, const char* prefix = 0,
const char* suffix = 0)
{
return buf.printf("%s%g%s",TelEngine::c_safe(prefix),val,TelEngine::c_safe(suffix));
}
static inline void getInterval(const String& s, int& iMin, int& iMax,
int minDef = INT_MIN, int maxDef = INT_MAX)
{
int pos = s.find('_');
if (pos >= 0) {
iMin = s.substr(0,pos).toInteger(minDef);
iMax = s.substr(pos + 1).toInteger(maxDef);
}
else {
iMin = s.toInteger(minDef);
iMax = maxDef;
}
if (iMin > iMax)
iMin = iMax;
}
static inline bool isInterval(int val, int iMin, int iMax, const String& interval)
{
if (interval)
getInterval(interval,iMin,iMax,iMin,iMax);
return (iMin <= val) && (val <= iMax);
}
static inline String& addIntervalInt(String& s, int minVal, int maxVal, const char* sep = " ")
{
String tmp;
return s.append(tmp.printf("[%d..%d]",minVal,maxVal),sep);
}
static inline bool retMsgError(NamedList& list, const char* what, const char* param = 0)
{
NamedString* ns = new NamedString("error",what);
if (!TelEngine::null(param))
*ns << " '" << param << "'";
list.setParam(ns);
return false;
}
static inline bool retParamError(NamedList& list, const char* param)
{
if (list.getParam(param))
return retMsgError(list,"Missing parameter",param);
return retMsgError(list,"Invalid parameter",param);
}
static inline bool retValFailure(NamedList& list, unsigned int code)
{
return retMsgError(list,String(code) + " " + RadioInterface::errorName(code));
}
static inline bool getFirstStr(String& dest, String& line)
{
int pos = line.find(' ');
if (pos >= 0) {
dest = line.substr(0,pos);
line = line.substr(pos + 1);
}
else {
dest = line;
line.clear();
}
return !dest.null();
}
// Convert 4 bytes to version string (MSB -> LSB: patch.minor.major)
static inline void ver2str(String& dest, uint32_t ver)
{
dest << (uint8_t)ver << ".";
dest << (uint8_t)(ver >> 8) << ".";
uint16_t patch = (uint8_t)(ver >> 16) | (uint8_t)(ver >> 24);
dest << patch;
}
// Code is expecting this array to have 15 elements
#define BRF_FILTER_BW_COUNT 16
#define BRF_FILTER_BW_MIN 1500000u
#define BRF_FILTER_BW_MAX 28000000u
static const uint32_t s_bandSet[BRF_FILTER_BW_COUNT] = {
BRF_FILTER_BW_MIN, 1750000u, 2500000u, 2750000u, 3000000u,
3840000u, 5000000u, 5500000u, 6000000u, 7000000u,
8750000u, 10000000u,12000000u,14000000u,20000000u,
BRF_FILTER_BW_MAX
};
static inline uint8_t bw2index(unsigned int value)
{
uint8_t i = 0;
for (; i < (BRF_FILTER_BW_COUNT - 1) && value > s_bandSet[i]; i++)
;
return i;
}
static inline unsigned int index2bw(uint8_t index)
{
return index < BRF_FILTER_BW_COUNT ? s_bandSet[index] : BRF_FILTER_BW_MAX;
}
#if 0
static uint32_t s_freqLimits[] = {
BRF_FREQUENCY_MIN, 285625000, 0x27,
285625000, 336875000, 0x2f,
336875000, 405000000, 0x37,
405000000, 465000000, 0x3f,
465000000, 571250000, 0x26,
571250000, 673750000, 0x2e,
673750000, 810000000, 0x36,
810000000, 930000000, 0x3e,
930000000, 1142500000, 0x25,
1142500000, 1347500000, 0x2d,
1347500000, 1620000000, 0x35,
1620000000, 1860000000, 0x3d,
1860000000u, 2285000000u, 0x24,
2285000000u, 2695000000u, 0x2c,
2695000000u, 3240000000u, 0x34,
3240000000u, BRF_FREQUENCY_MAX, 0x3c,
0,0,0
};
#else
static uint32_t s_freqLimits[] = {
BRF_FREQUENCY_MIN, 285625000, 0x27,
285625000, 336875000, 0x2f,
336875000, 405000000, 0x37,
405000000, 475000000, 0x3f,
475000000, 571250000, 0x26,
571250000, 673750000, 0x2e,
673750000, 810000000, 0x36,
810000000, 945000000, 0x3e,
945000000, 1142500000, 0x25,
1142500000, 1350000000, 0x2d,
1350000000, 1620000000, 0x35,
1620000000, 1890000000, 0x3d,
1890000000u, 2285000000u, 0x24,
2285000000u, 2695000000u, 0x2c,
2695000000u, 3240000000u, 0x34,
3240000000u, BRF_FREQUENCY_MAX, 0x3c,
0,0,0
};
#endif
struct BrfRationalRate
{
uint64_t integer;
uint64_t numerator;
uint64_t denominator;
};
struct Si5338MultiSynth
{
uint8_t index; // Multisynth to program (0-3)
uint16_t base; // Base address of the multisynth
BrfRationalRate requested; // Requested sample rates
BrfRationalRate actual; // Actual sample rates
uint8_t enable; // Enables for A and/or B outputs
uint32_t a, b, c, r; // f_out = fvco / (a + b/c) / r
uint32_t p1, p2, p3; // (a, b, c) in multisynth (p1, p2, p3) form
uint8_t regs[10]; // p1, p2, p3) in register form
};
static const uint8_t s_rxvga1_set[BRF_RXVGA1_GAIN_MAX + 1] = {
2, 2, 2, 2, 2, 2, 14, 26, 37, 47, 56, 63, 70, 76, 82, 87,
91, 95, 99, 102, 104, 107, 109, 111, 113, 114, 116, 117, 118, 119, 120,
};
static const uint8_t s_rxvga1_get[121] = {
5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10,
10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 13, 13,
13, 13, 13, 13, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, 16, 17,
17, 17, 18, 18, 18, 18, 19, 19, 19, 20, 20, 21, 21, 22, 22, 22, 23, 24, 24,
25, 25, 26, 27, 28, 29, 30
};
// Init radio caps with default values
static void initRadioCaps(RadioCapability& caps)
{
caps.maxPorts = 1;
caps.currPorts = 1;
caps.maxTuneFreq = BRF_FREQUENCY_MAX;
caps.minTuneFreq = BRF_FREQUENCY_MIN;
caps.maxSampleRate = BRF_SAMPLERATE_MAX;
caps.minSampleRate = BRF_SAMPLERATE_MIN;
caps.maxFilterBandwidth = BRF_FILTER_BW_MAX;
caps.minFilterBandwidth = BRF_FILTER_BW_MIN;
}
// Utility macros (call methods)
#define BRF_FUNC_CALL_(result,func,cond,instr) { \
result = func; \
if (cond) { instr; } \
}
// Call a method, remember the first error
#define BRF_FUNC_CALL(func) BRF_FUNC_CALL_(unsigned int tmp,func,!status && tmp,status = tmp; error = 0)
#define BRF_FUNC_CALL_BREAK(func) BRF_FUNC_CALL_(status,func,status,break)
#define BRF_FUNC_CALL_RET(func) BRF_FUNC_CALL_(status,func,status,return status)
static unsigned int threadIdleIntervals(unsigned int ms)
{
return 1 + ms / Thread::idleMsec();
}
static inline bool validFloatSample(float val)
{
return (val >= -1.0F) && (val <= 1.0F);
}
static inline void setMinMax(float& minF, float& maxF, float val)
{
if (maxF < val)
maxF = val;
if (minF > val)
minF = val;
}
static unsigned int checkSampleLimit(const float* buf, unsigned int samples, float limit,
String* error)
{
unsigned int n = 2 * samples;
for (unsigned int i = 0; i < n; ++i, ++buf)
if (*buf < -limit || *buf > limit) {
if (error)
error->printf("sample %c %f (at %u) out of range limit=%f",
brfIQ((i % 2) == 0),*buf,i / 2,limit);
return RadioInterface::Saturation;
}
return 0;
}
// Generate ComplexVector tone (exponential)
static void generateExpTone(ComplexVector& v, float omega, unsigned int len = 0)
{
if (len)
v.resetStorage(len);
for (unsigned int i = 0; i < v.length(); ++i) {
Complex c(0,i * omega);
v[i] = c.exp();
}
}
static String& replaceDumpParams(String& buf, NamedString* ns,
bool addRunParams = false, NamedString* ns1 = 0, NamedString* ns2 = 0)
{
NamedList p("");
p.addParam("newline","\r\n");
p.addParam("tab","\t");
if (ns)
p.addParam(ns);
p.addParam("sec_now",String(Time::secNow()));
char c[256];
Debugger::formatTime(c,Debugger::TextSep);
p.addParam("time",c);
if (addRunParams)
p.copyParams(Engine::runParams());
if (ns1)
p.addParam(ns1);
if (ns2)
p.addParam(ns2);
p.replaceParams(buf);
return buf;
}
// Allocate a new String. Replace params from format, return the new string
static inline String* replaceDumpParamsFmt(const String& fmt, NamedString* ns,
bool addRunParams = false, NamedString* ns1 = 0, NamedString* ns2 = 0)
{
String* s = new String(fmt);
replaceDumpParams(*s,ns,addRunParams,ns1,ns2);
return s;
}
// Dump Complex vector to a NamedString
static inline NamedString* dumpNsData(const ComplexVector& v, const char* name = "data")
{
NamedString* ns = new NamedString(name);
v.dump(*ns,Math::dumpComplex," ","%f%+fj");
return ns;
}
// Dump float vector to a NamedString
static inline NamedString* dumpNsData(const FloatVector& v, const char* name = "data")
{
NamedString* ns = new NamedString(name);
v.dump(*ns,Math::dumpFloat,",","%f");
return ns;
}
static inline bool boolSetError(String& s, const char* e = 0)
{
s = e;
return false;
}
// Parse a comma separated list of float values to complex vector
static bool parseVector(String& error, const String& str, ComplexVector& buf)
{
if (!str)
return boolSetError(error,"empty");
ObjList* list = str.split(',');
unsigned int len = list->length();
if ((len < 2) || (len % 2) != 0) {
TelEngine::destruct(list);
return boolSetError(error,"invalid length");
}
buf.resetStorage(len / 2);
ObjList* o = list;
for (float* b = (float*)buf.data(); o; o = o->next(), b++) {
if (!o->get())
continue;
*b = (static_cast<String*>(o->get()))->toDouble();
if (!validFloatSample(*b))
break;
}
TelEngine::destruct(list);
if (!o)
return true;
buf.resetStorage(0);
return boolSetError(error,"invalid data range");
}
static inline void generateCircleQuarter(Complex*& c, float amplitude, float i, float q,
unsigned int loops, float angle, float iSign, float qSign)
{
(c++)->set(i * amplitude,q * amplitude);
if (!loops)
return;
float angleStep = M_PI_2 / (loops + 1);
if (angle)
angleStep = -angleStep;
iSign *= amplitude;
qSign *= amplitude;
for (; loops; --loops, ++c) {
angle += angleStep;
c->set(iSign * ::cosf(angle),qSign * ::sinf(angle));
}
}
// Parse a complex numbers pattern
// forcePeriodic=true: Force lenExtend=false and lenRequired=true for periodic
// patterns (like 'circle')
// lenExtend=true: Extend destination buffer to be minimum 'len'. 'lenRequired' is ignored
// lenRequired=true: 'len' MUST be a multiple of generated vector's length
static bool buildVector(String& error, const String& pattern, ComplexVector& vector,
unsigned int len = 0, bool forcePeriodic = true, bool lenExtend = true,
bool lenRequired = false, unsigned int* pLen = 0, float gain=1)
{
if (!pattern)
return boolSetError(error,"empty");
bool isPeriodic = false;
String p = pattern;
ComplexVector v;
// Check for circles
if (p.startSkip("circle",false)) {
unsigned int cLen = 4;
bool rev = false;
float div = 1;
if (!p || p == YSTRING("_reverse"))
// circle[_reverse]
rev = !p.null();
else if (p.startSkip("_div_",false)) {
// circle_div[_reverse]_{divisor}
rev = p.startSkip("reverse_",false);
if (!p)
return boolSetError(error);
div = p.toDouble();
}
else if (p.startSkip("_points_",false)) {
// circle_points[_reverse]_{value}[_div_{divisor}]
rev = p.startSkip("reverse_",false);
if (!p)
return boolSetError(error);
int pos = p.find('_');
if (pos < 0)
cLen = p.toInteger(0,0,0);
else {
// Expecting div
cLen = p.substr(0,pos).toInteger(0,0,0);
p = p.substr(pos + 1);
if (!(p.startSkip("div_",false) && p))
return boolSetError(error);
div = p.toDouble();
}
}
else
return boolSetError(error);
// Circle length MUST be a multiple of 4
if (!cLen || (cLen % 4) != 0)
return boolSetError(error,"invalid circle length");
if (div < 1)
return boolSetError(error,"invalid circle div");
v.resetStorage(cLen);
Complex* c = v.data();
float amplitude = gain / div;
float direction = rev ? -1 : 1;
unsigned int n = (cLen - 4) / 4;
generateCircleQuarter(c,amplitude,1,0,n,0,1,direction);
generateCircleQuarter(c,amplitude,0,direction,n,M_PI_2,-1,direction);
generateCircleQuarter(c,amplitude,-1,0,n,0,-1,-direction);
generateCircleQuarter(c,amplitude,0,-direction,n,M_PI_2,1,-direction);
isPeriodic = true;
}
else if (pattern == YSTRING("zero")) {
// Fill with 0
vector.resetStorage(len ? len : 1);
if (pLen)
*pLen = 1;
return true;
}
else if (p.startSkip("fill_",false)) {
// Fill with value: fill_{real}_{imag}
int pos = p.find('_');
if (pos < 1 || p.find('_',pos + 1) > 0)
return boolSetError(error);
float re = p.substr(0,pos).toDouble();
float im = p.substr(pos + 1).toDouble();
if (validFloatSample(re) && validFloatSample(im)) {
vector.resetStorage(len ? len : 1);
vector.fill(Complex(re,im));
if (pLen)
*pLen = 1;
return true;
}
return boolSetError(error,"invalid data range");
}
else if (!parseVector(error,pattern,v))
// Parse list of values
return false;
if (!v.length())
return boolSetError(error,"empty result");
if (pLen)
*pLen = v.length();
if (isPeriodic && forcePeriodic) {
lenExtend = false;
lenRequired = true;
}
// Try to extend data
if (!len || (len == v.length()) || !(lenExtend || lenRequired))
vector = v;
else {
if (lenExtend) {
if (len < v.length())
len = v.length();
unsigned int rest = len % v.length();
if (rest)
len += v.length() - rest;
}
else if ((len < v.length()) || ((len % v.length()) != 0))
return boolSetError(error,"required/actual length mismatch");
vector.resetStorage(len);
for (unsigned int i = 0; (i + v.length()) <= len; i += v.length())
vector.slice(i,v.length()).copy(v,v.length());
}
return true;
}
static int16_t s_sampleEnergize = 2047;
// Energize a number. Refer the input value to the requested energy
static inline int16_t sampleScale(float value, float scale)
{
value *= scale;
return (int16_t)((value >= 0.0F) ? (value + 0.5F) : (value - 0.5F));
}
// len is number of complex samples (I&Q pairs)
static bool energize(const float* samples, int16_t* dest,
const float iScale, const float qScale, const unsigned len)
{
if (len % 2 != 0) {
Debug("bladerf",DebugFail,"Energize len %u must be a multiple of 2",len);
return false;
}
// len is number of complex pairs
// N is number of scalars
const unsigned N = len * 2;
#ifdef __MMX__
const float rescale = 32767.0 / s_sampleEnergize;
const float is2 = iScale * rescale;
const float qs2 = qScale * rescale;
// Intel intrinstics
int32_t i32[4];
const __m64* s32A = (__m64*) &i32[0];
const __m64* s32B = (__m64*) &i32[2];
for (unsigned i = 0; i < N; i += 4) {
// apply I/Q correction and scaling and convert samples to 32 bits
// gcc -O2/-O3 on intel uses cvttss2si or cvttps2dq depending on the processor
i32[0] = is2 * samples[i + 0];
i32[1] = qs2 * samples[i + 1];
i32[2] = is2 * samples[i + 2];
i32[3] = qs2 * samples[i + 3];
// saturate 32 bits to 16
// process 4 16-bit samples in a 64-bit block
__m64* d64 = (__m64*) &dest[i];
*d64 = _mm_packs_pi32(*s32A, *s32B);
}
// shift 16 bit down to 12 bits for BladeRF
// This has to be done after saturation.
for (unsigned i = 0; i < N; i++)
dest[i] = dest[i] >> 4;
#else
for (unsigned i = 0; i < N; i += 2) {
// scale and saturate
float iv = iScale * samples[i];
if (iv > 2047)
iv = 2047;
else if (iv < -2047)
iv = -2047;
float qv = qScale * samples[i + 1];
if (qv > 2047)
qv = 2047;
else if (qv < -2047)
qv = -2047;
// convert and save
dest[i] = iv;
dest[i + 1] = qv;
}
#endif
return true;
}
static inline void brfCopyTxData(int16_t* dest, const float* src, const unsigned samples,
const float scaleI, int16_t maxI, const float scaleQ, int16_t maxQ, unsigned int& clamped,
const long* ampTable=NULL)
{
// scale and convert to 12-bit integers
if (!energize(src, dest, scaleI, scaleQ, samples)) {
::memset(dest,0,2 * samples * sizeof(int16_t));
return;
}
if (ampTable) {
int16_t *d1 = dest;
const int16_t *s1 = dest;
for (unsigned i = 0; i < samples; i++) {
// amplifier predistortion
// power of the sample, normalized to the energy scale
// this has a range of 0 .. (2*scale)-1
long xRe = *s1++;
long xIm = *s1++;
unsigned p = (xRe * xRe + xIm * xIm) >> 10; // 2 * (xRe*xRe + xIm*xIm)/2048
// get the correction factor, abs of 0..1
long corrRe = ampTable[p];
long corrIm = ampTable[p+1];
// apply the correction factor (complex multiplication), rescaled by 2048
*d1++ = (corrRe * xRe - corrIm * xIm) >> 11;
*d1++ = (corrRe * xIm + corrIm * xRe) >> 11;
}
}
if (htole16(0x1234) != 0x1234)
for (unsigned i = 0; i < samples; i++)
dest[i] = htole16(dest[i]);
}
class BrfDuration
{
public:
inline BrfDuration(uint64_t start = Time::now())
: m_start(start), m_durationUs(0)
{}
inline void stop() {
if (!m_durationUs)
m_durationUs = Time::now() - m_start;
}
inline const char* secStr() {
stop();
m_str.printf("%u.%usec",(unsigned int)(m_durationUs / 1000000),
(unsigned int)(m_durationUs % 1000000) / 1000);
return m_str;
}
protected:
uint64_t m_start;
uint64_t m_durationUs;
String m_str;
};
class BrfDumpFile
{
public:
inline BrfDumpFile(const NamedList* p = 0, const char* fName = 0,
bool createAlways = false)
: m_dumpOk(0), m_dumpFail(0), m_tmpDumpOk(0), m_tmpDumpFail(0),
m_newFile(false) {
if (p)
init(*p,fName,createAlways);
}
~BrfDumpFile()
{ writeData(true); }
inline bool valid() const
{ return m_file.valid(); }
inline const String& fileName() const
{ return m_fileName; }
inline const File& file() const
{ return m_file; }
inline bool dumpHeader()
{ return (m_newFile && valid()) ? !(m_newFile = false) : false; }
inline bool dumpOk() const
{ return m_tmpDumpOk != 0; }
inline bool dumpFail() const
{ return m_tmpDumpFail != 0; }
inline void resetDumpOkFail() {
m_tmpDumpOk = m_dumpOk;
m_tmpDumpFail = m_dumpFail;
}
inline void append(String* s) {
if (s && *s)
m_dump.append(s);
else
TelEngine::destruct(s);
}
inline void appendFormatted(const FloatVector& data, const String& fmt)
{ append(replaceDumpParamsFmt(fmt,dumpNsData(data))); }
inline void appendFormatted(const ComplexVector& data, bool ok) {
const String& fmt = ok ? m_dumpFmtOk : m_dumpFmtFail;
if (!fmt)
return;
append(replaceDumpParamsFmt(fmt,dumpNsData(data)));
int& what = ok ? m_tmpDumpOk : m_tmpDumpFail;
if (what > 0)
what--;
}
// Dump vector data if format parameter is present
inline void dumpDataFmt(const ComplexVector& v, const NamedList& params,
const String& fmtParam) {
const String& fmt = params[fmtParam];
if (fmt)
append(replaceDumpParamsFmt(fmt,dumpNsData(v)));
}
inline bool init(const NamedList& p, const char* fName, bool createAlways = false) {
writeData(true);
if (TelEngine::null(fName))
fName = p[YSTRING("dump_file")];
if (TelEngine::null(fName))
return false;
m_fileName = fName;
replaceDumpParams(m_fileName,0,true);
m_newFile = false;
if (createAlways || !m_file.openPath(m_fileName,true)) {
if (!m_file.openPath(m_fileName,true,false,true,false,false,true,true))
return false;
m_newFile = true;
}
else if (m_file.seek(Stream::SeekEnd) < 0) {
m_file.terminate();
return false;
}
m_dumpFmtOk = p[YSTRING("dump_buf_ok_format")];
m_dumpFmtFail = p[YSTRING("dump_buf_fail_format")];
m_dumpOk = m_dumpFmtOk ? p.getIntValue(YSTRING("dump_buf_ok")) : 0;
m_dumpFail = m_dumpFmtFail ? p.getIntValue(YSTRING("dump_buf_fail")) : 0;
resetDumpOkFail();
return true;
}
inline void writeData(bool finalize = false) {
if (!valid())
return;
if (m_dump.skipNull()) {
String buf;
buf.append(m_dump);
m_dump.clear();
if (buf)
m_file.writeData(buf.c_str(),buf.length());
}
if (finalize)
m_file.terminate();
}
protected:
int m_dumpOk;
int m_dumpFail;
int m_tmpDumpOk;
int m_tmpDumpFail;
String m_dumpFmtOk;
String m_dumpFmtFail;
ObjList m_dump;
bool m_newFile;
File m_file;
String m_fileName;
};
class BrfPeripheral : public String
{
public:
inline BrfPeripheral(const char* name, uint8_t devId)
: String(name),
m_devId(devId), m_tx(false), m_rx(false), m_haveTrackAddr(false),
m_trackLevel(-1) {
lowCase = name;
lowCase.toLower();
setTrack(false,false);
}
inline uint8_t devId() const
{ return m_devId; }
inline bool trackDir(bool tx) const
{ return tx ? m_tx : m_rx; }
inline bool haveTrackAddr() const
{ return m_haveTrackAddr; }
inline int trackLevel(int level = DebugAll) const
{ return (m_trackLevel >= 0) ? m_trackLevel : level; }
void setTrack(bool tx, bool rx, const String& addr = String::empty(), int level = -1);
// Check for addr track range, return first match addr or -1 if not found
inline int isTrackRange(uint8_t addr, uint8_t len) const {
for (; addr < sizeof(m_trackAddr) && len; len--, addr++)
if (m_trackAddr[addr])
return addr;
return -1;
}
inline bool isTrackAddr(uint8_t addr) const
{ return addr < sizeof(m_trackAddr) && m_trackAddr[addr]; }
String lowCase;
protected:
uint8_t m_devId; // Device id
bool m_tx;
bool m_rx;
bool m_haveTrackAddr;
uint8_t m_trackAddr[128];
int m_trackLevel;
};
// Device calibration data
class BrfCalData
{
public:
inline BrfCalData(int mod) {
::memset(this,0,sizeof(BrfCalData));
module = mod;
desc = &s_calModuleDesc[module];
}
inline const char* modName() const
{ return calModName(module); }
int module;
const BrfCalDesc* desc;
uint8_t clkEn;
uint8_t inputMixer;
uint8_t loOpt;
uint8_t lnaGain;
int rxVga1;
int rxVga2;
uint8_t rxVga2GainAB;
// LPF_BANDWIDTH
uint8_t txVGA2PwAmp;
uint8_t txPPL;
uint8_t enLPFCAL;
uint8_t clkLPFCAL;
uint8_t nInt;
uint8_t nFrac1;
uint8_t nFrac2;
uint8_t nFrac3;
};
// Thresholds used to adjust the number of internal buffers from sampling rate
class BrfBufsThreshold
{
public:
inline BrfBufsThreshold()
: sampleRate(0), bufferedSamples(0), txMinBufs(0)
{}
unsigned int sampleRate;
unsigned int bufferedSamples;
unsigned int txMinBufs;
static const char* init(DataBlock& db, const String& str, const RadioCapability& caps);
static BrfBufsThreshold* findThres(DataBlock& db, unsigned int sampleRate);
};
// libusb transfer
class LusbTransfer : public Mutex
{
public:
inline LusbTransfer(BrfLibUsbDevice* dev = 0)
: Mutex(false,"LusbTransfer"),
device(dev), ep(255), transfer(0), status(0), m_running(false)
{}
inline ~LusbTransfer()
{ reset(); }
inline bool running() const
{ return m_running; }
inline void running(bool start) {
m_running = start;
if (m_running) {
status = 0;
error.clear();
}
}
inline bool alloc() {
if (transfer)
return true;
transfer = ::libusb_alloc_transfer(0);
if (transfer)
return true;
error = "Failed to allocate libusb transfer";
status = RadioInterface::Failure;
return false;
}
inline void reset() {
cancel();
if (transfer)
::libusb_free_transfer(transfer);
transfer = 0;
m_running = false;
}
// Fill bulk transfer. Allocate if not already done
// Return true on success (change state to Init)
bool fillBulk(uint8_t* data, unsigned int len, unsigned int tout);
bool submit();
unsigned int cancel(String* error = 0);
BrfLibUsbDevice* device;
uint8_t ep;
libusb_transfer* transfer;
unsigned int status;
String error;
protected:
bool m_running;
};
// Holds RX/TX related data
// Hold samples read/write related data
class BrfDevDirState
{
public:
inline BrfDevDirState(bool tx)
: showDcOffsChange(0), showFpgaCorrChange(0), showPowerBalanceChange(0),
rfEnabled(false), frequency(0), vga1(0), vga1Changed(false), vga2(0), lpf(0),
dcOffsetI(0), dcOffsetQ(0), fpgaCorrPhase(0), fpgaCorrGain(0),
powerBalance(0), lpfBw(0), sampleRate(0), m_timestamp(0),
m_tx(tx)
{}
inline BrfDevDirState(const BrfDevDirState& src)
{ *this = src; }
inline BrfDevDirState& operator=(const BrfDevDirState& src) {
::memcpy(this,&src,sizeof(src));
return *this;
}
inline bool tx() const
{ return m_tx; }
unsigned int showDcOffsChange; // Show DC offset changed debug message
unsigned int showFpgaCorrChange; // Show FPGA PHASE/GAIN changed debug message
unsigned int showPowerBalanceChange; // Show power balance changed debug message
bool rfEnabled; // RF enabled flag
unsigned int frequency; // Used frequency
int vga1; // VGA1 gain
bool vga1Changed; // VGA1 was set by us
int vga2; // VGA2 gain
int lpf; // LPF status
int dcOffsetI; // Current I (in-phase) DC offset
int dcOffsetQ; // Current Q (quadrature) DC offset
int fpgaCorrPhase; // Current FPGA phase correction
int fpgaCorrGain; // Current FPGA gain correction
float powerBalance; // Current power balance
unsigned int lpfBw; // LPF bandwidth
unsigned int sampleRate; // Sampling rate
uint64_t m_timestamp;
protected:
bool m_tx; // Direction
};
// Holds device data. May be used to backup and restore
class BrfDevState
{
public:
inline BrfDevState(unsigned int chg = 0, unsigned int txChg = 0,
unsigned int rxChg = 0)
: m_changed(chg), m_txChanged(txChg), m_rxChanged(rxChg),
m_loopback(0), m_loopbackParams(""), m_txPatternGain(1), m_rxDcAuto(true),
m_tx(true), m_rx(false)
{}
inline BrfDevState(const BrfDevState& src, unsigned int chg = 0,
unsigned int txChg = 0, unsigned int rxChg = 0)
: m_loopbackParams(""), m_tx(true), m_rx(false) {
assign(src,false);
setFlags(chg,txChg,rxChg);
}
inline void setFlags(unsigned int chg = 0, unsigned int txChg = 0,
unsigned int rxChg = 0) {
m_changed = chg;
m_txChanged = txChg;
m_rxChanged = rxChg;
}
inline void setLoopback(int lp, const NamedList& params) {
m_loopback = lp;
m_loopbackParams.clearParams();
m_loopbackParams.copyParams(params);
}
inline BrfDevState& assign(const BrfDevState& src, bool flags = true) {
if (flags)
setFlags(src.m_changed,src.m_txChanged,src.m_rxChanged);
else
setFlags();
setLoopback(src.m_loopback,src.m_loopbackParams);
m_txPattern = src.m_txPattern;
m_txPatternGain = src.m_txPatternGain;
m_rxDcAuto = src.m_rxDcAuto;
m_tx = src.m_tx;
m_rx = src.m_rx;
return *this;
}
inline BrfDevState& operator=(const BrfDevState& src)
{ return assign(src); }
unsigned int m_changed; // Changed flags
unsigned int m_txChanged; // TX data changed flags
unsigned int m_rxChanged; // RX data changed flags
int m_loopback; // Current loopback
NamedList m_loopbackParams; // Loopback params
String m_txPattern; // Transmit pattern
float m_txPatternGain; // Transmit pattern gain
bool m_rxDcAuto; // Automatically adjust Rx DC offset
BrfDevDirState m_tx;
BrfDevDirState m_rx;
};
class BrfFloatMinMax
{
public:
inline BrfFloatMinMax()
: value(0), min(BRF_MAX_FLOAT), max(-BRF_MAX_FLOAT)
{}
inline void set(float val) {
value = val;
setMinMax(min,max,val);
}
inline void reset(float val = 0) {
value = val;
min = BRF_MAX_FLOAT;
max = -BRF_MAX_FLOAT;
}
inline operator float()
{ return value; }
float value;
float min;
float max;
};
class BrfFloatAccum
{
public:
inline BrfFloatAccum()
: count(0)
{}
inline void append(float val)
{ data[count++] = val; }
inline void reset(unsigned int len) {
data.resetStorage(len);
count = 0;
}
inline void normalize()
{ data.resize(count); }
FloatVector data;
unsigned int count;
};
struct BrfBbCalDataResult
{
inline BrfBbCalDataResult()
: status(0), cal(0), test(0), total(0),
test_total(0), cal_test(0),testOk(false), calOk(false)
{}
unsigned int status;
float cal;
float test;
float total;
float test_total; // test / total
float cal_test; // cal / test
bool testOk;
bool calOk;
};
class BrfBbCalData
{
public:
inline BrfBbCalData(unsigned int nSamples, const NamedList& p)
: m_stopOnRecvFail(0), m_repeatRxLoop(5),
m_best(0), m_cal_test(0),
m_prevCal(0), m_testOk(false), m_calOk(false), m_params(p),
m_tx(true), m_rx(false),
m_calFreq(0), m_calSampleRate(0),
m_dcI(0), m_dcQ(0), m_phase(0), m_gain(0),
m_buffer(nSamples), m_calTone(nSamples), m_testTone(nSamples),
m_calToneOmega(0), m_testToneOmega(0) {
prepareCalculate();
m_stopOnRecvFail = p.getIntValue(YSTRING("recv_fail_stop"),1);
m_repeatRxLoop = p.getIntValue(YSTRING("recv_fail_loops"),5,1,1000);
}
inline const String& prefix(bool dc) const {
static const String s_dcPrefix = "dc_";
static const String s_imbalancePrefix = "imbalance_";
return dc ? s_dcPrefix : s_imbalancePrefix;
}
inline float omega(bool cal) const
{ return cal ? m_calToneOmega : m_testToneOmega; }
inline float* buf() const
{ return (float*)m_buffer.data(); }
inline unsigned int samples() const
{ return m_buffer.length(); }
inline ComplexVector& buffer()
{ return m_buffer; }
inline const ComplexVector& calTone() const
{ return m_calTone; }
inline const ComplexVector& testTone() const
{ return m_testTone; }
inline void prepareCalculate() {
m_best = BRF_MAX_FLOAT;
m_prevCal = 0;
m_cal.reset(-1);
m_total.reset();
m_test.reset();
}
inline void resetBuffer(unsigned int nSamples)
{ resetOmega(m_calToneOmega,m_testToneOmega,nSamples); }
inline void resetOmega(float calToneOmega, float testToneOmega, unsigned int nSamples = 0) {
if (nSamples)
m_buffer.resetStorage(nSamples);
m_calToneOmega = calToneOmega;
m_testToneOmega = testToneOmega;
generateExpTone(m_calTone,calToneOmega,m_buffer.length());
generateExpTone(m_testTone,testToneOmega,m_buffer.length());
}
inline void setResult(BrfBbCalDataResult& res) {
m_prevCal = m_cal.value;
m_cal.set(res.cal);
m_test.set(res.test);
m_total.set(res.total);
m_cal_test = res.cal_test;
m_test_total.set(res.test_total);
m_calOk = res.calOk;
m_testOk = res.testOk;
}
inline bool calculate(BrfBbCalDataResult& res) {
const Complex* last = 0;
const Complex* b = m_buffer.data(0,m_buffer.length(),last);
const Complex* calTone = m_calTone.data();
const Complex* testTone = m_testTone.data();
Complex calSum;
Complex testSum;
res.total = 0;
// Calculate calibrate/test energy using the narrow band integrator
// Calculate total buffer energy (power)
for (; b != last; ++b, ++calTone, ++testTone) {
calSum += *calTone * *b;
testSum += *testTone * *b;
res.total += b->norm2();
}
res.cal = calSum.norm2() / samples();
res.test = testSum.norm2() / samples();
res.cal_test = res.test ? (res.cal / res.test) : -1;
res.test_total = res.total ? (res.test / res.total) : -1;
res.calOk = 0.0F <= res.cal_test && res.cal_test <= 0.001F;
res.testOk = 0.5F < res.test_total && res.test_total <= 1.0F;
#if 0
res.test /= samples();
res.total /= samples();
#endif
return res.testOk;
}
inline String& dump(String& s, bool full) {
float delta = 0;
if (m_prevCal >= 0.0F)
delta = m_cal.value - m_prevCal;
const char* dir = dirStr(delta ? (delta > 0.0F ? 1 : -1) : 0);
if (full)
return s.printf(1024,"%s cal:%-10f test:%-10f total:%-10f "
"test/total:%3s %.2f%% cal/test:%3s %.2f%%",
dir,m_cal.value,m_test.value,m_total.value,
(m_testOk ? "OK" : "BAD"),m_test_total.value * 100,
(m_calOk ? "OK" : "BAD"),m_cal_test * 100);
return s.printf(1024,"%s cal:%-10f delta=%-10f",dir,m_cal.value,delta);
}
inline String& dump(String& s, const BrfBbCalDataResult& res) {
return s.printf(1024,"cal:%-10f test:%-10f total:%-10f "
"test/total:%3s %.2f%% cal/test:%3s %.2f%%",
res.cal,res.test,res.total,(res.testOk ? "OK" : "BAD"),
res.test_total * 100,(res.calOk ? "OK" : "BAD"),res.cal_test * 100);
}
inline const String& param(bool dc, const char* name) const
{ return m_params[prefix(dc) + name]; }
inline unsigned int uintParam(bool dc, const char* name, unsigned int defVal = 0,
unsigned int minVal = 0, unsigned int maxVal = (unsigned int)LLONG_MAX) const
{ return param(dc,name).toInt64(defVal,0,minVal,maxVal); }
inline int intParam(bool dc, const char* name, int defVal = 0,
int minVal = INT_MIN, int maxVal = INT_MAX) const
{ return param(dc,name).toInteger(defVal,0,minVal,maxVal); }
inline bool boolParam(bool dc, const char* name, bool defVal = false) const
{ return param(dc,name).toBoolean(defVal); }
void initCal(BrfLibUsbDevice& dev, bool dc, String& fName);
void finalizeCal(const String& result);
void dumpCorrStart(unsigned int pass, int corr, int corrVal, int fixedCorr,
int fixedCorrVal, unsigned int range, unsigned int step,
int calValMin, int calValMax);
void dumpCorrEnd(bool dc);
int m_stopOnRecvFail; // Stop on data recv wrong result
unsigned int m_repeatRxLoop; // Repeat data read on wrong result
float m_best;
BrfFloatMinMax m_cal; // Calculated calibrating value
BrfFloatMinMax m_total; // Calculated total value
BrfFloatMinMax m_test; // Calculated test value
BrfFloatMinMax m_test_total; // Calculated test/total value
float m_cal_test; // cal / test
float m_prevCal; // Previous calibrating value
bool m_testOk;
bool m_calOk;
NamedList m_params; // Calibration parameters
BrfFloatAccum m_calAccum;
BrfFloatAccum m_testAccum;
BrfFloatAccum m_totalAccum;
BrfDumpFile m_dump;
// Initial state
BrfDevDirState m_tx;
BrfDevDirState m_rx;
// Calibration params
unsigned int m_calFreq;
unsigned int m_calSampleRate;
// Calibration results
int m_dcI;
int m_dcQ;
int m_phase;
int m_gain;
protected:
ComplexVector m_buffer;
ComplexVector m_calTone;
ComplexVector m_testTone;
float m_calToneOmega;
float m_testToneOmega;
};
// Holds RX/TX related data
// Hold samples read/write related data
class BrfDevIO
{
public:
inline BrfDevIO(bool tx)
: showBuf(0), showBufData(true), checkTs(0), dontWarnTs(0), checkLimit(0),
mutex(false,tx ? "BrfDevIoTx" : "BrfDevIoRx"),
startTime(0), transferred(0),
timestamp(0), lastTs(0), buffers(0), hdrLen(0), bufSamples(0),
bufSamplesLen(0), crtBuf(0), crtBufSampOffs(0), newBuffer(true),
dataDumpParams(""), dataDump(0), dataDumpFile(brfDir(tx)),
upDumpParams(""), upDump(0), upDumpFile(String(brfDir(tx)) + "-APP"),
captureMutex(false,tx ? "BrfCaptureTx" : "BrfCaptureRx"),
captureSemaphore(1,tx ? "BrfCaptureTx" : "BrfCaptureRx",1),
captureBuf(0), captureSamples(0),
captureTs(0), captureOffset(0), captureStatus(0),
m_tx(tx), m_bufEndianOk(BRF_LITTLE_ENDIAN)
{}
inline bool tx() const
{ return m_tx; }
void resetSamplesBuffer(unsigned int nSamples, unsigned int hLen,
unsigned int nBuffers = 1) {
bufSamples = nSamples;
bufSamplesLen = samplesi2bytes(bufSamples);
hdrLen = hLen;
bufLen = hdrLen + bufSamplesLen;
buffers = nBuffers ? nBuffers : 1;
buffer.assign(0,buffers * bufLen);
resetPosTime();
}
inline void resetPosTime() {
resetBufPos(m_tx);
timestamp = 0;
lastTs = 0;
startTime = 0;
transferred = 0;
}
inline void reset()
{ resetPosTime(); }
inline bool advanceBuffer() {
if (crtBuf < buffers)
setCrtBuf(crtBuf + 1);
newBuffer = true;
return crtBuf < buffers;
}
inline uint8_t* bufStart(unsigned int index)
{ return buffer.data(index * bufLen); }
inline int16_t* samples(unsigned int index)
{ return (int16_t*)(bufStart(index) + hdrLen); }
inline int16_t* samplesEOF(unsigned int index)
{ return (int16_t*)(bufStart(index) + bufLen); }
// Retrieve a pointer to current buffer samples start (including offset)
// and available samples number
inline int16_t* crtBufSamples(unsigned int& avail) {
avail = bufSamples - crtBufSampOffs;
return samples(crtBuf) + crtBufSampOffs * 2;
}
inline uint64_t bufTs(unsigned int index) {
// Skip reserved
uint32_t* u = (uint32_t*)(bufStart(index) + 4);
// Get timestamp (LOW32 + HI32, little endian)
uint64_t ts = le32toh((uint32_t)(*u++ >> 1));
return ts | ((uint64_t)le32toh(*u) << 31);
}
inline void setBufTs(unsigned int index, uint64_t ts) {
uint32_t* u = (uint32_t*)(bufStart(index));
*u++ = htole32(0xdeadbeef);
*u++ = htole32((uint32_t)(ts << 1));
*u++ = htole32((uint32_t)(ts >> 31));
*u = htole32((uint32_t)-1);
}
inline void resetBufPos(bool start = true) {
#ifndef LITTLE_ENDIAN
m_bufEndianOk = false;
#endif
setCrtBuf(start ? 0 : buffers);
newBuffer = true;
}
inline void fixEndian() {
#ifndef LITTLE_ENDIAN
if (m_bufEndianOk)
return;
m_bufEndianOk = true;
uint8_t* d = buffer.data(0);
for (unsigned int i = 0; i < buffers; i++) {
d += hdrLen;
for (uint8_t* last = d + bufSamplesLen; d != last; d += 2) {
uint8_t tmp = *d;
*d = d[1];
d[1] = tmp;
}
}
#endif
}
inline void dumpInt16Samples(String& s, unsigned int index, unsigned int sampOffs = 0,
int nSamples = -1) {
int16_t* p = samples(index) + sampOffs * 2;
unsigned int n = bufSamples - sampOffs;
if (nSamples > 0 && nSamples < (int)n)
n = nSamples;
for (int16_t* last = (p + n * 2); p != last; p += 2) {
if (s)
s << " ";
s << p[0] << "," << p[1];
}
}
int showBuf; // Show buffers
bool showBufData; // Display buffer data
int checkTs; // Check IO buffers timestamp
int dontWarnTs; // Don't warn on invalid buffer timestamp
int checkLimit; // Check IO buffers sample limit
Mutex mutex; // Protect data changes when needed
uint64_t startTime; // Absolute time for start (first TX/RX)
uint64_t transferred; // The number of samples transferred
// TX/RX data
uint64_t timestamp; // Last timestamp to/from device
uint64_t lastTs; // Last buffer timestamp (used when checking)
unsigned int buffers; // The number of buffers
unsigned int hdrLen; // Header length in bytes
unsigned int bufSamples; // Length of a single buffer in samples (without header)
unsigned int bufSamplesLen; // Length of a single buffer in bytes (without header)
unsigned int bufLen; // Length of a single buffer (in bytes)
unsigned int crtBuf; // Current buffer
unsigned int crtBufSampOffs; // Current buffer samples offset
bool newBuffer; // New buffer to process
DataBlock buffer; // I/O buffer
BrfBufsThreshold firstBufsThres; // Initial samplerate/bufs data
// File dump
NamedList dataDumpParams;
int dataDump;
RadioDataFile dataDumpFile;
NamedList upDumpParams;
int upDump;
RadioDataFile upDumpFile;
// Capture
Mutex captureMutex;
Semaphore captureSemaphore;
float* captureBuf;
unsigned int captureSamples;
uint64_t captureTs;
unsigned int captureOffset;
unsigned int captureStatus;
String captureError;
protected:
// Reset current buffer to start
inline void setCrtBuf(unsigned int index = 0) {
crtBuf = index;
crtBufSampOffs = 0;
}
bool m_tx;
bool m_bufEndianOk;
};
// Temporary change alt setting. Restore on destruction
class BrfDevTmpAltSet
{
public:
inline BrfDevTmpAltSet(BrfLibUsbDevice* dev)
: m_device(dev), m_oper(0), m_tmpAltSet(BRF_ALTSET_INVALID)
{}
inline BrfDevTmpAltSet(BrfLibUsbDevice* dev, int altSet,
unsigned int& status, String* error, const char* oper)
: m_device(dev), m_oper(0), m_tmpAltSet(BRF_ALTSET_INVALID)
{ status = set(altSet,error,oper); }
// Temporary change to RF_LINK
inline BrfDevTmpAltSet(BrfLibUsbDevice* dev, unsigned int& status, String* error,
const char* oper)
: m_device(dev), m_oper(0), m_tmpAltSet(BRF_ALTSET_INVALID)
{ status = set(BRF_ALTSET_RF_LINK,error,oper); }
inline ~BrfDevTmpAltSet()
{ restore(); }
unsigned int set(int altSet, String* error, const char* oper);
inline unsigned int set(String* error, const char* oper)
{ return set(BRF_ALTSET_RF_LINK,error,oper); }
unsigned int restore();
private:
BrfLibUsbDevice* m_device;
const char* m_oper;
int m_tmpAltSet;
};
/**
* Clock discipline algorithm to sync the BladeRF's VCTCXO to local machine's clock.
* It's intended to maintain (measure and correct) sampling rate drifts,
* caused by equipment aging and factory faults, within a desired range.
*
* The accuracy of the measured drift is highly impacted by these factors:
* - the drift of the local machine's clock against the real time
* - the accuracy of pinning radio board samples to local machine timestamps
* - the time allocated for measuring the average sampling rate (a.k.a. baseline)
*/
class BrfVctcxoDiscipliner {
public:
/// Create a discipliner with default settings, waiting to be activated and configured through control messages.
BrfVctcxoDiscipliner()
: m_trimsLeft(0), m_confSampleRate(0), m_freqOffset(0), m_resumePoint(0),
m_samples(0), m_timestamp(0), m_delay(0), m_bestDelay(0), m_maxDelay(0),
m_knownDelay(0), m_systemAccuracy(BRF_SYSTEM_ACCURACY_DEF), m_accuracyPpb(BRF_ACCURACY_PPB_DEF),
m_nextPinning(0), m_driftPpb(0), m_trace(false), m_dumpDelays(0)
{ }
/// Handle clock discipline commands
bool onCmdFreqCal(Message& msg, bool start = true);
/// Postpone activity for the specified period and drop current data
void postponeActivity(unsigned minutes, bool dropData = false);
/// Stop activity and drop gathered data
void disableDiscipline(bool onCmd = false);
/**
* Discipline the BladeRF's VCTCXO to local machine's clock
* This method should be called when the BrfModule catches an engine.timer message
* @param timestamp the call time in microseconds
* @param drift a drift expressed in ppb, to be forcefully corrected
*/
void trimVctcxo(uint64_t timestamp = Time::now(), int drift = 0);
protected:
/// Update the baseline interval
void scheduleNextPinning(uint16_t delay);
/// Update parameters and drop current data if the configuration is outdated
bool outdatedConfig();
/// If it's missing, do the initial sample measurement
bool init();
/// Trim the VCTCXO based on the measured drift and dispatch a message with the new frequency offset
bool processData(int drift);
/// Compute the average radio sampling rate and return the drift (ppb) for the current interval
int measureDrift(uint64_t& samples, uint64_t& timestamp, uint16_t& delay);
/// Get the most accurate radio board sample measurement out of a given number
void samplesAndTimestamp(uint64_t& samples, uint64_t& timestamp, uint16_t& delay,
unsigned maxIter = 10);
/// Convert microseconds to minutes (floor division)
inline unsigned usToMin(uint64_t us)
{ return us / 60000000UL; }
/// The BladeRF device associated with this object
virtual BrfLibUsbDevice& dev() =0;
int m_trimsLeft; ///< number of scheduled clock trims, 0 toggles to idle, -1 toggles to always active
unsigned m_confSampleRate; ///< configured sampling rate (Hz)
float m_freqOffset; ///< frequency offset, as a number converted from VCTCXO's voltage to pass through a digital to analog converter
uint64_t m_resumePoint; ///< when postponed, activity resumes after this timestamp (microsec)
uint64_t m_samples; ///< initial pinning of radio board samples
uint64_t m_timestamp; ///< initial pinning timestamp (microsec)
uint16_t m_delay; ///< initial pinning duration (microsec)
uint16_t m_bestDelay; ///< minimum pinning delay (microsec), used to identify highly accurate measurements
uint16_t m_maxDelay; ///< maximum pinning delay (microsec), used to identify highly inaccurate measurements
uint16_t m_knownDelay; ///< fixed, known pinning delay (microsec), used as reference for assuming delay variations
uint16_t m_systemAccuracy; ///< overall accuracy of the sync mechanism (microsec)
unsigned m_accuracyPpb; ///< accuracy of the sampling rate measurement, expressed in ppb
uint64_t m_nextPinning; ///< final pinning of samples is scheduled after this timestamp (microsec)
int m_driftPpb; ///< drift of the sampling rate, expressed in ppb
bool m_trace; ///< this variable is used in test mode to display debug messages
unsigned int m_dumpDelays; ///< number of delays to calculate and dump
String m_delayStat; ///< store delays every few seconds, then dump it for analysis
static const float s_ppbPerUnit; ///< the unit of m_freqOffset expressed as ppb
// 1.9 for max ppm range, expressed in ppb
// 1.25 for voltage range conversion of 0.4 - 2.4V to 0 - 2.5V
// 2 ** 8 for integer range of m_freqOffset
};
class BrfThread;
class BrfSerialize;
class BrfLibUsbDevice : public GenObject, public BrfVctcxoDiscipliner
{
friend class BrfDevTmpAltSet;
friend class BrfThread;
friend class BrfModule;
friend class BrfInterface;
friend class BrfSerialize;
friend class BrfDevState;
friend class BrfBbCalData;
friend class BrfVctcxoDiscipliner;
public:
enum UartDev {
UartDevGPIO = 0,
UartDevLMS,
UartDevVCTCXO,
UartDevSI5338,
UartDevCount
};
enum Endpoint {
EpSendSamples = 0,
EpSendCtrl,
EpReadSamples,
EpReadCtrl,
EpCount
};
// LNA selection
enum LmsLna {
LmsLnaNone = 0, // Disable all LNAs
LmsLna1, // Enable LNA1 (300MHz - 2.8GHz)
LmsLna2, // Enable LNA2 (1.5GHz - 3.8GHz)
LmsLna3, // Enable LNA3 (Unused on the bladeRF)
LmsLnaDetect,
};
// PA Selection
enum LmsPa {
LmsPaNone = 0, // All PAs disabled
LmsPa1, // PA1 Enable (300MHz - 2.8GHz)
LmsPa2, // PA2 Enable (1.5GHz - 3.8GHz)
LmsPaAux, // AUX PA Enable (for RF Loopback)
};
// LNA gain values
enum LnaGain {
LnaGainUnhandled = 0,
LnaGainBypass = 1,
LnaGainMid = 2,
LnaGainMax = 3,
};
// Correction types (LMS and FPGA). Keep them in the same order
// (values are used as array index)
enum CorrectionType {
CorrLmsI = 0,
CorrLmsQ,
CorrFpgaPhase,
CorrFpgaGain,
CorrCount
};
// Loopback mode
enum Loopback {
LoopNone = 0, // Disabled
LoopFirmware, // Firmware loopback
LoopLpfToRxOut, // Baseband: TX LPF -> RX out
LoopLpfToVga2, // Baseband: TX LPF -> RX VGA2
LoopVga1ToVga2, // Baseband: TX VGA1 -> RX VGA2
LoopLpfToLpf, // Baseband: TX LPF -> RX LPF
LoopVga1ToLpf, // Baseband: TX VGA1 -> RX LPF
LoopRfLna1, // RF: mixer after PA -> LNA1
LoopRfLna2, // RF: mixer after PA -> LNA2
LoopRfLna3, // RF: mixer after PA -> LNA3
LoopUnknown
};
enum Lpf {
LpfInvalid = 0,
LpfDisabled = 1,
LpfBypass,
LpfNormal,
};
// Flags used to restore dev status
enum StatusFlags {
DevStatFreq = 0x00000001,
DevStatVga1 = 0x00000002,
DevStatVga2 = 0x00000004,
DevStatLpf = 0x00000008,
DevStatDcI = 0x00000010,
DevStatDcQ = 0x00000020,
DevStatLpfBw = 0x00000040,
DevStatSampleRate = 0x00000080,
DevStatFpgaPhase = 0x00000100,
DevStatFpgaGain = 0x00000200,
DevStatLoopback = 0x00000400,
DevStatRxDcAuto = 0x00000800,
DevStatTxPattern = 0x00001000,
DevStatTs = 0x00002000,
DevStatPowerBalance = 0x10000000,
DevStatAbortOnFail = 0x80000000,
DevStatVga = DevStatVga1 | DevStatVga2,
DevStatDc = DevStatDcI | DevStatDcQ,
DevStatFpga = DevStatFpgaPhase | DevStatFpgaGain,
};
// Calibration status
enum CalStatus {
Calibrate = 0, // Not calibrated (not done or failed)
Calibrated, // Succesfully calibrated
Calibrating, // Calibration in progress
};
~BrfLibUsbDevice();
inline BrfInterface* owner() const
{ return m_owner; }
inline libusb_device_handle* handle() const
{ return m_devHandle; }
inline RadioCapability& capabilities()
{ return m_radioCaps; }
inline bool validPort(unsigned int port) const
{ return port < m_radioCaps.currPorts; }
inline int speed() const
{ return m_devSpeed; }
inline const char* speedStr() const
{ return speedStr(speed()); }
inline int bus() const
{ return m_devBus; }
inline int addr() const
{ return m_devAddr; }
inline const String& address() const
{ return m_address; }
inline const String& serial() const
{ return m_devSerial; }
inline const String& fwVerStr() const
{ return m_devFwVerStr; }
inline const String& fpgaFile() const
{ return m_devFpgaFile; }
inline const String& fpgaMD5() const
{ return m_devFpgaMD5; }
inline const String& fpgaVerStr() const
{ return m_devFpgaVerStr; }
inline const String& lmsVersion() const
{ return m_lmsVersion; }
inline bool exiting() const
{ return m_exiting; }
inline void exiting(bool on)
{ m_exiting = on; }
inline bool closing() const
{ return m_closing; }
inline unsigned int cancelled(String* error = 0) {
if (exiting() || closing()) {
if (error)
*error = "Exiting";
return RadioInterface::Cancelled;
}
return checkCancelled(error);
}
inline int showBuf(bool tx, int val, bool tsOnly) {
Lock lck(m_dbgMutex);
getIO(tx).showBufData = !tsOnly;
return (getIO(tx).showBuf = val);
}
inline int checkTs(bool tx, int val) {
Lock lck(m_dbgMutex);
return (getIO(tx).checkTs = val);
}
inline int checkLimit(bool tx, int val) {
Lock lck(m_dbgMutex);
return (getIO(tx).checkLimit = val);
}
inline int showRxDCInfo(int val) {
Lock lck(m_dbgMutex);
return (m_rxShowDcInfo = val);
}
inline unsigned int bufSamples(bool tx)
{ return getIO(tx).bufSamples; }
inline unsigned int bufCount(bool tx)
{ return getIO(tx).buffers; }
inline unsigned int totalSamples(bool tx) {
BrfDevIO& io = getIO(tx);
return io.buffers * io.bufSamples;
}
inline float freqOffset() const
{ return m_freqOffset; }
// Open (on=false)/close RXOUTSW switch
inline unsigned int setRxOut(bool on)
{ return writeLMS(0x09,on ? 0x80 : 0x00,0x80); }
unsigned int setTxPattern(const String& pattern, float gain = 1.0F);
void dumpStats(String& buf, const char* sep);
void dumpTimestamps(String& buf, const char* sep);
void dumpDev(String& buf, bool info, bool state, const char* sep,
bool fromStatus = false, bool withHdr = true);
void dumpBoardStatus(String& buf, const char* sep);
unsigned int dumpPeripheral(uint8_t dev, uint8_t addr, uint8_t len, String* buf = 0);
// Module reload
void reLoad(const NamedList* params = 0);
void setDataDump(int dir = 0, int level = 0, const NamedList* params = 0);
// Open the device
// Call the reset method in order to set the device to a known state
unsigned int open(const NamedList& params, String& error);
// Initialize operating parameters
unsigned int initialize(const NamedList& params);
// Check if parameters are set
unsigned int isInitialized(bool tx, bool rx, String* error);
// Close the device.
void close();
// Power on the radio
// Enable timestamps, enable RF TX/RX
unsigned int powerOn();
// Send an array of samples waiting to be transmitted
// samples: The number of I/Q samples (i.e. half buffer lengh)
unsigned int syncTx(uint64_t ts, float* data, unsigned int samples,
float* powerScale = 0, bool internal = false);
// Receive data from the Rx interface of the bladeRF device
// samples: The number of I/Q samples (i.e. half buffer lengh)
unsigned int syncRx(uint64_t& ts, float* data, unsigned int& samples,
String* error = 0, bool internal = false);
// Capture data
unsigned int capture(bool tx, float* buf, unsigned int samples, uint64_t& ts,
String* error = 0);
// Set the frequency on the Tx or Rx side
unsigned int setFrequency(uint64_t hz, bool tx);
// Retrieve frequency
unsigned int getFrequency(uint32_t& hz, bool tx);
// Set frequency offset
unsigned int setFreqOffset(float offs, float* newVal = 0, bool stopAutoCal = true);
// Get frequency offset
unsigned int getFreqOffset(float& offs);
// Set the LPF bandwidth for a specific module
unsigned int setLpfBandwidth(uint32_t band, bool tx);
// Get the LPF bandwidth for a specific module
unsigned int getLpfBandwidth(uint32_t& band, bool tx);
// LPF set/get
unsigned int setLpf(int lpf, bool tx);
unsigned int getLpf(int& lpf, bool tx);
// Set the sample rate on a specific module
unsigned int setSamplerate(uint32_t value, bool tx);
// Get the sample rate on a specific module
unsigned int getSamplerate(uint32_t& value, bool tx);
// Set the pre-mixer gain on transmission (interval [-35..-4])
// Set the post-mixer gain setting on transmission (interval: [0..25])
unsigned int setTxVga(int vga, bool preMixer);
// Set the post-mixer gain setting on transmission (interval: [0..25])
inline unsigned int setTxVga1(int vga)
{ return setTxVga(vga,true); }
inline unsigned int setTxVga2(int vga)
{ return setTxVga(vga,false); }
// Retrieve the pre/post mixer gain setting on transmission
unsigned int getTxVga(int& vga, bool preMixer);
inline unsigned int getTxVga1(int& vga)
{ return getTxVga(vga,true); }
inline unsigned int getTxVga2(int& vga)
{ return getTxVga(vga,false); }
// Set TX power balance
unsigned int setTxIQBalance(float value);
// Enable or disable the pre/post mixer gain on the receive side
unsigned int enableRxVga(bool on, bool preMixer);
inline unsigned int enableRxVga1(bool on)
{ return enableRxVga(on,true); }
inline unsigned int enableRxVga2(bool on)
{ return enableRxVga(on,false); }
// Set the pre-mixer RX gain setting on the receive side (interval [5..30])
// Set the post-mixer RX gain setting (interval [0..30])
unsigned int setRxVga(int vga, bool preMixer);
inline unsigned int setRxVga1(int vga)
{ return setRxVga(vga,true); }
inline unsigned int setRxVga2(int vga)
{ return setRxVga(vga,false); }
// Retrieve the pre/post mixer rx gain setting
unsigned int getRxVga(int& vga, bool preMixer);
inline unsigned int getRxVga1(int& vga)
{ return getRxVga(vga,true); }
inline unsigned int getRxVga2(int& vga)
{ return getRxVga(vga,false); }
// Set pre and post mixer value
unsigned int setGain(bool tx, int val, int* newVal = 0);
// Run check / calibration procedure
unsigned int calibrate(bool sync = true, const NamedList& params = NamedList::empty(),
String* error = 0, bool fromInit = false);
// Set Tx/Rx DC I/Q offset correction
unsigned int setDcOffset(bool tx, bool i, int16_t value);
// Retrieve Tx/Rx DC I/Q offset correction
unsigned int getDcOffset(bool tx, bool i, int16_t& value);
// Set/Get FPGA correction
unsigned int setFpgaCorr(bool tx, int corr, int16_t value);
unsigned int getFpgaCorr(bool tx, int corr, int16_t& value);
// Retrieve TX/RX timestamp
unsigned int getTimestamp(bool tx, uint64_t& ts);
// Retrieve TX radio timestamp (samples) with the current timestamp from the local machine
unsigned int samplesAndTimestamp(uint64_t& samples, uint64_t& timestamp, uint16_t& delay,
String* serializeErr);
// Write LMS register(s)
unsigned int writeLMS(uint8_t addr, uint8_t value, uint8_t* rst = 0,
String* error = 0, bool internal = false);
unsigned int writeLMS(uint8_t addr, uint8_t value, uint8_t rst,
String* error = 0, bool internal = false)
{ return writeLMS(addr,value,&rst,error,internal); }
unsigned int writeLMS(const String& str, String* error = 0, bool internal = false);
// Read LMS register(s)
unsigned int readLMS(uint8_t addr, uint8_t& value, String* error = 0,
bool internal = false);
unsigned int readLMS(String& dest, const String* read, bool readIsInterleaved,
String* error = 0, bool internal = false);
// Check LMS registers
unsigned int checkLMS(const String& what, String* error = 0, bool internal = false);
// Enable or disable loopback
unsigned int setLoopback(const char* name = 0,
const NamedList& params = NamedList::empty());
// Set parameter(s)
unsigned int setParam(const String& param, const String& value,
const NamedList& params = NamedList::empty());
// Utility: run device send data
void runSend(BrfThread* th);
// Utility: run device recv data
void runRecv(BrfThread* th);
// Build notification message
Message* buildNotify(const char* status = 0);
inline void notifyFreqOffs() {
Message* m = buildNotify();
m->addParam("RadioFrequencyOffset",String(m_freqOffset));
Engine::enqueue(m);
}
// Release data
virtual void destruct();
// Utilities
static const char* speedStr(int speed) {
switch (speed) {
case LIBUSB_SPEED_SUPER:
return "SUPER";
case LIBUSB_SPEED_HIGH:
return "HIGH";
}
return "Unknown";
}
static uint64_t reduceFurther(uint64_t v1, uint64_t v2);
static void reduceRational(BrfRationalRate& rate);
static inline void rationalDouble(BrfRationalRate& rate) {
rate.integer *= 2;
rate.numerator *= 2;
reduceRational(rate);
}
static void calcSrate(Si5338MultiSynth& synth, BrfRationalRate& rate);
static unsigned int calcMultiSynth(Si5338MultiSynth& synth,
BrfRationalRate& rate, String* error = 0);
static void packRegs(Si5338MultiSynth& synth);
static void unpackRegs(Si5338MultiSynth& synth);
// Set error string
static inline unsigned int setError(unsigned int code, String* buf,
const char* error, const char* prefix = 0) {
if (!(buf && code))
return code;
String tmp = prefix;
tmp.append((error && *error) ? error : RadioInterface::errorName(code)," - ");
buf->append(tmp," - ");
return code;
}
static inline unsigned int setErrorFail(String* buf, const char* error,
const char* prefix = 0)
{ return setError(RadioInterface::Failure,buf,error,prefix); }
static inline unsigned int setErrorTimeout(String* buf, const char* error,
const char* prefix = 0)
{ return setError(RadioInterface::Timeout,buf,error,prefix); }
static inline unsigned int setErrorNotInit(String* buf,
const char* error = "not initialized", const char* prefix = 0)
{ return setError(RadioInterface::NotInitialized,buf,error,prefix); }
static inline unsigned int setUnkValue(String& buf, const char* unsupp = 0,
const char* invalid = 0) {
if (unsupp)
buf << "Unsupported " << unsupp;
else if (invalid)
buf << "Invalid " << invalid;
else
buf << "Unknown value";
return RadioInterface::OutOfRange;
}
static inline unsigned int setUnhandled(String& buf, int val, const char* what = 0) {
buf << "Unhandled";
buf.append(what," ");
buf << val;
return RadioInterface::OutOfRange;
}
// Print libusb error
static inline String& appendLusbError(String& buf, int code, const char* prefix = 0) {
buf << prefix << "(" << code << " '" << ::libusb_error_name(code) << "')";
return buf;
}
static unsigned int lusb2ifaceError(int code);
// Utility: check libusb result against LIBUSB_SUCCESS
// Print libusb result to string on failure and return radio iface code
static inline unsigned int lusbCheckSuccess(int code, String* error,
const char* prefix = 0) {
if (code == LIBUSB_TRANSFER_COMPLETED || code == LIBUSB_SUCCESS)
return 0;
if (error)
appendLusbError(*error,code,prefix);
return lusb2ifaceError(code);
}
// Retrieve UART addr for FPGA correction
static inline uint8_t fpgaCorrAddr(bool tx, bool phase) {
if (phase)
return tx ? 10 : 6;
return tx ? 8 : 4;
}
// Retrieve LMS addr for I/Q correction
static inline uint8_t lmsCorrIQAddr(bool tx, bool i) {
if (tx)
return i ? 0x42 : 0x43;
return i ? 0x71 : 0x72;
}
// Retrieve LMS addr for TX/RX VGA 1/2
static inline uint8_t lmsVgaAddr(bool tx, bool preMixer) {
if (tx)
return preMixer ? 0x41 : 0x45;
return preMixer ? 0x76 : 0x65;
}
// Retrieve LMS LPF base address
static inline uint8_t lmsLpfAddr(bool tx)
{ return tx ? 0x34 : 0x54; }
// Retrieve LMS PLL freq config addr
static inline uint8_t lmsFreqAddr(bool tx)
{ return tx ? 0x10 : 0x20; }
protected:
virtual BrfLibUsbDevice& dev()
{ return *this; }
private:
BrfLibUsbDevice(BrfInterface* owner);
void doClose();
inline void resetTimestamps(bool tx) {
getIO(tx).reset();
if (!tx) {
m_rxTimestamp = 0;
m_rxResyncCandidate = 0;
}
}
// Batch state update
unsigned int setState(BrfDevState& state, String* error = 0);
// Request changes (synchronous TX). Wait for change
inline unsigned int setStateSyncTx(unsigned int flags = 0, String* error = 0,
bool fatal = true) {
m_syncTxState.setFlags(fatal ? DevStatAbortOnFail : 0,flags);
return setStateSync(error);
}
inline unsigned int setStateSyncRx(unsigned int flags = 0, String* error = 0,
bool fatal = true) {
m_syncTxState.setFlags(fatal ? DevStatAbortOnFail : 0,0,flags);
return setStateSync(error);
}
inline unsigned int setStateSyncLoopback(int lp, const NamedList& params,
String* error = 0) {
m_syncTxState.setFlags(DevStatLoopback);
m_syncTxState.setLoopback(lp,params);
return setStateSync(error);
}
unsigned int setStateSync(String* error = 0);
void internalDumpDev(String& buf, bool info, bool state, const char* sep,
bool internal, bool fromStatus = false, bool withHdr = true);
unsigned int internalPowerOn(bool rfLink, bool tx = true, bool rx = true,
String* error = 0);
// Send an array of samples waiting to be transmitted
// samples: The number of I/Q samples (i.e. half buffer lengh)
unsigned int send(uint64_t ts, float* data, unsigned int samples,
float* powerScale = 0);
void sendTxPatternChanged();
void sendCopyTxPattern(int16_t* buf, unsigned int avail,
float scaleI, int16_t maxI, float scaleQ, int16_t maxQ, unsigned int& clamped,
const long* ampTable);
unsigned int recv(uint64_t& ts, float* data, unsigned int& samples,
String* error = 0);
void captureHandle(BrfDevIO& io, const float* buf, unsigned int samples, uint64_t ts,
unsigned int status, const String* error);
unsigned int internalSetSampleRate(bool tx, uint32_t value, String* error = 0);
inline unsigned int internalSetSampleRateBoth(uint32_t value, String* error = 0) {
unsigned int status = internalSetSampleRate(true,value,error);
return status ? status : internalSetSampleRate(false,value,error);
}
// Update FPGA (load, get version)
unsigned int updateFpga(const NamedList& params);
unsigned int internalSetFpgaCorr(bool tx, int corr, int16_t value,
String* error = 0, int clampLevel = DebugNote);
unsigned int internalGetFpgaCorr(bool tx, int corr, int16_t* value = 0,
String* error = 0);
unsigned int internalSetTxVga(int vga, bool preMixer, String* error = 0);
unsigned int internalGetTxVga(int* vga, bool preMixer, String* error = 0);
// Enable or disable the pre/post mixer gain on the receive side
unsigned int internalEnableRxVga(bool on, bool preMixer, String* error = 0);
unsigned int internalSetRxVga(int vga, bool preMixer, String* error = 0);
unsigned int internalGetRxVga(int* vga, bool preMixer, String* error = 0);
inline unsigned int internalRxVga(bool read, int& vga, bool preMixer,
String* error = 0) {
if (read)
return internalGetRxVga(&vga,preMixer,error);
return internalSetRxVga(vga,preMixer,error);
}
inline unsigned int internalSetVga(bool tx, int vga, bool preMixer,
String* error = 0) {
if (tx)
return internalSetTxVga(vga,preMixer,error);
return internalSetRxVga(vga,preMixer,error);
}
unsigned int internalSetGain(bool tx, int val, int* newVal = 0, String* error = 0);
unsigned int internalSetTxIQBalance(bool newGain, float newBalance = 0,
const char* param = 0);
unsigned int setGainExp(float breakpoint, float max);
unsigned int setPhaseExp(float breakpoint, float max);
inline unsigned int internalSetCorrectionIQ(bool tx, int I, int Q,
String* error = 0) {
unsigned int status = internalSetDcOffset(tx,true,I,error);
return !status ? internalSetDcOffset(tx,false,Q,error) : status;
}
inline unsigned int internalSetDcCorr(int txI, int txQ, int rxI, int rxQ,
String* error = 0) {
unsigned int status = 0;
BRF_FUNC_CALL(internalSetCorrectionIQ(true,txI,txQ,error));
BRF_FUNC_CALL(internalSetCorrectionIQ(false,rxI,rxQ,error));
return status;
}
unsigned int internalSetFreqOffs(float val, float* newVal, String* error = 0);
unsigned int internalSetFrequency(bool tx, uint64_t val, String* error = 0);
unsigned int internalGetFrequency(bool tx, uint32_t* hz = 0, String* error = 0);
// Retrieve TX/RX timestamp
unsigned int internalGetTimestamp(bool tx, uint64_t& ts, String* error = 0);
// Retrieve and set frequency
inline unsigned int restoreFreq(bool tx, String* error = 0) {
uint32_t hz = 0;
unsigned int status = internalGetFrequency(tx,&hz,error);
return status == 0 ? internalSetFrequency(tx,hz,error) : status;
}
// Alt interface setting change
unsigned int lusbSetAltInterface(int altSetting, String* error = 0);
// Wrapper for libusb_control_transfer
unsigned int lusbCtrlTransfer(uint8_t reqType, int8_t request, uint16_t value,
uint16_t index, uint8_t* data, uint16_t len, String* error = 0,
unsigned int tout = 0);
// Wrapper for libusb synchronous bulk transfer
unsigned int lusbBulkTransfer(uint8_t endpoint, uint8_t* data, unsigned int len,
unsigned int* transferred = 0, String* error = 0, unsigned int tout = 0);
// Make an async usb transfer
unsigned int syncTransfer(int ep, uint8_t* data, unsigned int len, String* error = 0);
// Select amplifier (PA/LNA) from low/high frequency
inline unsigned int selectPaLna(bool tx, bool lowBand, String* error) {
if (tx)
return paSelect(lowBand,error);
return lnaSelect(lowBand ? LmsLna1 : LmsLna2,error);
}
// Read the value of a specific GPIO register
unsigned int gpioRead(uint8_t addr, uint32_t& value, uint8_t len, String* error = 0,
const char* loc = 0);
// Write a value to a specific GPIO register
unsigned int gpioWrite(uint8_t addr, uint32_t value, uint8_t len, String* error = 0,
const char* loc = 0);
// Read the lms configuration
inline unsigned int lmsRead(uint8_t addr, uint8_t& data, String* error = 0,
const char* loc = 0)
{ return accessPeripheralRead(UartDevLMS,addr,data,error,loc); }
inline unsigned int lmsRead2(uint8_t addr1, uint8_t& data1,
uint8_t addr2, uint8_t& data2, String* error = 0, const char* loc = 0) {
unsigned int status = lmsRead(addr1,data1,error,loc);
return status == 0 ? lmsRead(addr2,data2,error,loc) : status;
}
// Write the lms configuration
unsigned int lmsWrite(const String& str, bool updStat, String* error = 0);
// Read the lms configuration
// Read all if 'read' is null
// 'read' non null: set 'readIsInterleaved' to true if 'read' is addr/value interleaved
unsigned int lmsRead(String& dest, const String* read,
bool readIsInterleaved, String* error = 0);
// Check LMS registers
unsigned int lmsCheck(const String& what, String* error = 0);
inline unsigned int lmsWrite(uint8_t addr, uint8_t data, String* error = 0,
const char* loc = 0)
{ return accessPeripheralWrite(UartDevLMS,addr,data,error,loc); }
inline unsigned int lmsWrite2(uint8_t addr1, uint8_t data1,
uint8_t addr2, uint8_t data2, String* error = 0, const char* loc = 0) {
unsigned int status = lmsWrite(addr1,data1,error,loc);
return status == 0 ? lmsWrite(addr2,data2,error,loc) : status;
}
inline unsigned int lms(bool read, uint8_t addr, uint8_t& data,
String* error = 0, const char* loc = 0) {
if (read)
return lmsRead(addr,data,error,loc);
return lmsWrite(addr,data,error,loc);
}
// Read address from LMS, clear mask, set val (using OR) and write it back
inline unsigned int lmsSet(uint8_t addr, uint8_t val, uint8_t clearMask,
String* error = 0) {
uint8_t data = 0;
unsigned int status = lmsRead(addr,data,error);
return status ? status : lmsWrite(addr,(data & ~clearMask) | val,error);
}
// Read address from LMS, set val (using OR) and write it back
inline unsigned int lmsSet(uint8_t addr, uint8_t val, String* error = 0) {
uint8_t data = 0;
unsigned int status = lmsRead(addr,data,error);
return status ? status : lmsWrite(addr,data | val,error);
}
// Read address from LMS, clear mask and write it back
inline unsigned int lmsReset(uint8_t addr, uint8_t clearMask, String* error = 0) {
uint8_t data = 0;
unsigned int status = lmsRead(addr,data,error);
return status ? status : lmsWrite(addr,(data & ~clearMask),error);
}
// Reset LMS addr using mask. Optionally set a value
inline unsigned int lmsChange(uint8_t addr, uint8_t* maskReset, uint8_t* maskSet,
String* error) {
if (maskReset && maskSet)
return lmsSet(addr,*maskSet,*maskReset,error);
if (maskReset)
return lmsReset(addr,*maskReset,error);
return maskSet ? lmsSet(addr,*maskSet,error) : 0;
}
inline unsigned int lmsChangeMask(uint8_t addr, uint8_t mask, bool set,
String* error)
{ return lmsChange(addr,set ? 0 : &mask,set ? &mask : 0,error); }
// LNA
unsigned int lnaSelect(int lna, String* error = 0);
unsigned int lnaEnable(bool on, String* error = 0);
unsigned int lnaGainSet(uint8_t value, String* error = 0);
unsigned int lnaGainGet(uint8_t& value, String* error = 0);
inline unsigned int lnaGain(bool read, uint8_t& value, String* error = 0) {
if (read)
return lnaGainGet(value,error);
return lnaGainSet(value,error);
}
// LPF set/get
unsigned int internalSetLpfBandwidth(bool tx, uint32_t band, String* error = 0);
inline unsigned int internalSetLpfBandwidthBoth(uint32_t band, String* error = 0) {
unsigned int status = internalSetLpfBandwidth(true,band,error);
if (!status)
status = internalSetLpfBandwidth(false,band,error);
return status;
}
unsigned int internalSetLpf(bool tx, int lpf, String* error = 0);
unsigned int internalGetLpf(bool tx, int* lpf, String* error = 0);
// Fill the m_list member of the class
unsigned int updateDeviceList(String* error = 0);
inline void clearDeviceList() {
if (!m_list)
return;
::libusb_free_device_list(m_list,1);
m_dev = 0;
m_list = 0;
m_listCount = 0;
}
// Enable/disable RF and sample circulation on both RX and TX sides
inline unsigned int enableRfBoth(bool on, bool frontEndOnly, String* error = 0) {
unsigned int status = enableRf(true,on,frontEndOnly,error);
if (status == 0)
return enableRf(false,on,frontEndOnly,error);
return status;
}
inline unsigned int enableRfFpgaBoth(bool on, String* error = 0) {
unsigned int status = enableRfFpga(true,on,error);
if (status == 0)
return enableRfFpga(false,on,error);
return status;
}
unsigned int enableRf(bool tx, bool on, bool frontEndOnly = false, String* error = 0);
unsigned int enableRfFpga(bool tx, bool on, String* error = 0);
// Check if fpga is loaded
// Return NoError/NotInitialized (result OK) or other error on failure
unsigned int checkFpga();
// Restore device after loading the FPGA
unsigned int restoreAfterFpgaLoad(String* error = 0);
// Change some LMS registers default value on open
unsigned int openChangeLms(const NamedList& params, String* error = 0);
// Reset the Usb interface using an ioctl call
unsigned int resetUsb(String* error = 0);
// Set the VCTCXO configuration to the correct value
unsigned int tuneVcocap(uint8_t addr, String* error = 0);
// Send requests to the bladeRF device regarding the FPGA image configuration.
unsigned int vendorCommand(uint8_t cmd, uint8_t endpoint, uint8_t* data, uint16_t len,
String* error = 0);
inline unsigned int vendorCommand(uint8_t cmd, uint8_t endpoint, int32_t& data,
String* error = 0)
{ return vendorCommand(cmd,endpoint,(uint8_t*)&data,sizeof(data),error); }
inline unsigned int vendorCommand0_4(uint8_t cmd, uint8_t endpoint, String* error = 0) {
uint32_t dummy = 0;
return vendorCommand(cmd,endpoint,(uint8_t*)&dummy,4,error);
}
// Access the bladeRF board in order to transmit data
unsigned int accessPeripheral(uint8_t dev, bool tx, uint8_t addr,
uint8_t* data, String* error = 0, uint8_t len = 1, const char* loc = 0);
inline unsigned int accessPeripheralWrite(uint8_t dev, uint8_t addr, uint8_t data,
String* error = 0, const char* loc = 0)
{ return accessPeripheral(dev,true,addr,&data,error,1,loc); }
inline unsigned int accessPeripheralRead(uint8_t dev, uint8_t addr, uint8_t& data,
String* error = 0, const char* loc = 0)
{ return accessPeripheral(dev,false,addr,&data,error,1,loc); }
inline unsigned int setSi5338(uint8_t addr, uint8_t data, String* error = 0)
{ return accessPeripheralWrite(UartDevSI5338,addr,data,error); }
inline unsigned int getSi5338(uint8_t addr, uint8_t& data, String* error = 0)
{ return accessPeripheralRead(UartDevSI5338,addr,data,error); }
unsigned int internalSetDcOffset(bool tx, bool i, int16_t value, String* error = 0);
unsigned int internalGetDcOffset(bool tx, bool i, int16_t* value, String* error = 0);
unsigned int enableTimestamps(bool on = true, String* error = 0);
unsigned int updateStatus(String* error = 0);
inline unsigned int paSelect(bool lowBand, String* error = 0)
{ return paSelect(lowBand ? LmsPa1 : LmsPa2,error); }
unsigned int paSelect(int pa, String* error = 0);
int64_t clampInt(int64_t val, int64_t minVal, int64_t maxVal, const char* what = 0,
int level = DebugNote);
inline int64_t clampIntParam(const NamedList& params, const String& param,
int64_t defVal, int64_t minVal, int64_t maxVal, int level = DebugConf)
{ return clampInt(params.getInt64Value(param,defVal),minVal,maxVal,param,level); }
float clampFloat(float val, float minVal, float maxVal, const char* what = 0,
int level = DebugNote);
inline float clampFloatParam(const NamedList& params, const String& param,
float defVal, float minVal, float maxVal, int level = DebugConf)
{ return clampFloat(params.getDoubleValue(param,defVal),minVal,maxVal,param,level); }
unsigned int openDevice(bool claim, String* error = 0);
void closeDevice();
void closeUsbDev();
void getDevStrDesc(String& data, uint8_t index, const char* what);
// Read pages from device using contrt to 0 (from -35)ol transfer
unsigned int ctrlTransferReadPage(uint8_t request, DataBlock& buf, String* error = 0);
// Read calibration cache page from device
unsigned int readCalCache(String* error = 0);
// Retrieve a filed from a buffer of elements
// Buffer format: 1 byte length + data + 2 bytes CRC16
// Return error string on failure
const char* getBufField(String& value, const char* field);
// Read calibration cache field
unsigned int getCalField(String& value, const String& name,
const char* desc = 0, String* error = 0);
String& dumpCalCache(String& dest);
// Update speed related data
unsigned int updateSpeed(const NamedList& params, String* error = 0);
// Set I/O buffers
void initBuffers(bool* txSet, unsigned int totalSamples, unsigned int txMinSend);
// Compute Rx avg values, autocorrect offsets if configured
void computeRx(uint64_t ts);
// Check io timestamps
void ioBufCheckTs(bool tx, unsigned int nBufs = 0);
void setIoDontWarnTs(bool tx);
// Check io samples limit
void ioBufCheckLimit(bool tx, unsigned int nBufs = 0);
// Alter data
void updateAlterData(const NamedList& params);
void rxAlterData(bool first);
// Calibration utilities
void dumpState(String& s, const NamedList& data, bool lockPub, bool force = false);
unsigned int calibrateAuto(String* error);
unsigned int calBackupRestore(BrfCalData& bak, bool backup, String* error);
unsigned int calInitFinal(BrfCalData& bak, bool init, String* error);
unsigned int dcCalProcPrepare(const BrfCalData& bak, uint8_t subMod, String& error);
unsigned int dcCalProc(const BrfCalData& bak, uint8_t subMod, uint8_t dcCnt,
uint8_t& dcReg, String& error);
unsigned int dcCalProcPost(const BrfCalData& bak, uint8_t subMod, uint8_t dcReg,
String& error);
unsigned int calLPFBandwidth(const BrfCalData& bak, uint8_t subMod, uint8_t dcCnt,
uint8_t& dcReg, String& error);
unsigned int calibrateBbCorrection(BrfBbCalData& data, int corr, int range, int step,
int pass, String* error);
unsigned int prepareCalibrateBb(BrfBbCalData& data, bool dc, String* error);
unsigned int calibrateBb(BrfBbCalData& data, bool dc, String* error);
unsigned int calibrateBaseband(String* error);
// amplifier linearization
ComplexVector sweepPower(float startdB, float stopdB, float stepdB);
unsigned int findGainExpParams(const ComplexVector& sweep, float startSweep, float stepSweep);
unsigned int findPhaseExpParams(const ComplexVector& swee, float startSweep, float stepSweepp);
unsigned int calculateAmpTable();
//
unsigned int loopbackCheck(String* error);
unsigned int testVga(const char* loc, bool tx, bool preMixer, float omega = 0,
String* error = 0);
inline unsigned int testVgaCheck(const NamedList& p, const char* loc,
float omega, String* error, const String& prefix = String::empty()) {
unsigned int status = 0;
#define BRF_TEST_VGA(param,tx,preMixer) \
if (!status && p.getBoolValue(prefix + param)) \
status = testVga(loc,tx,preMixer,omega,error);
BRF_TEST_VGA("test_tx_vga1",true,true);
BRF_TEST_VGA("test_tx_vga2",true,false);
BRF_TEST_VGA("test_rx_vga1",false,true);
BRF_TEST_VGA("test_rx_vga2",false,false);
#undef BRF_TEST_VGA
return status;
}
// Set error string or put a debug message
unsigned int showError(unsigned int code, const char* error, const char* prefix,
String* buf, int level = DebugNote);
void printIOBuffer(bool tx, const char* loc, int index = -1, unsigned int nBufs = 0);
void dumpIOBuffer(BrfDevIO& io, unsigned int nBufs);
void updateIODump(BrfDevIO& io);
inline BrfDevIO& getIO(bool tx)
{ return tx ? m_txIO : m_rxIO; }
inline BrfDevDirState& getDirState(bool tx)
{ return tx ? m_state.m_tx : m_state.m_rx; }
inline unsigned int checkDbgInt(int& val, unsigned int step = 1) {
if (!(val && step))
return 0;
Lock lck(m_dbgMutex);
if (val < 0)
return step;
if (val >= (int)step)
val -= step;
else {
step = val;
val = 0;
}
return step;
}
// Enable or disable loopback
unsigned int internalSetLoopback(int mode = 0,
const NamedList& params = NamedList::empty(), String* error = 0);
unsigned int setLoopbackPath(int mode, String& error);
void dumpLoopbackStatus(String* dest = 0);
void dumpLmsModulesStatus(String* dest = 0);
unsigned int internalDumpPeripheral(uint8_t dev, uint8_t addr, uint8_t len,
String* buf, uint8_t lineLen);
inline int decodeLpf(uint8_t reg1, uint8_t reg2) const {
int on = reg1 & (1 << 1);
int bypass = reg2 & (1 << 6);
if (on)
return bypass ? LpfInvalid : LpfNormal;
return bypass ? LpfBypass : LpfDisabled;
}
// Set RXVGA2 DECODE bit (LMS addr 0x64 bit 0)
// 0(true): Decode control signals
// 1(false): Use control signal from test mode registers
inline unsigned int setRxVga2Decode(bool on, String* error)
{ return on ? lmsReset(0x64,0x01,error) : lmsSet(0x64,0x01,error); }
inline bool setRxDcAuto(bool value) {
if (m_state.m_rxDcAuto != value)
return !(m_state.m_rxDcAuto = value);
return m_state.m_rxDcAuto;
}
inline unsigned int getRxSamples(const NamedList& p, const char* name = "samples") {
unsigned int n = p.getIntValue(name,totalSamples(false),0);
if (n < 1000)
return 1000;
if ((n % 4) == 0)
return n;
return n + 4 - (n % 4);
}
// Start internal threads
unsigned int startCalibrateThreads(String* error,
const NamedList& params = NamedList::empty());
// Pause/resume I/O internal threads pause
unsigned int calThreadsPause(bool on, String* error = 0);
// Stop internal threads
void stopThreads();
inline unsigned int checkDev(const char* loc) {
return m_devHandle ? 0 :
showError(RadioInterface::NotInitialized,"not open",loc,0,DebugCrit);
}
inline unsigned int checkCalStatus(const char* loc) {
return (Calibrating != m_calibrateStatus) ? 0 :
showError(RadioInterface::NotCalibrated,"calibrating",loc,0,DebugCrit);
}
inline unsigned int checkPubFuncEntry(bool internal, const char* loc) {
unsigned int status = 0;
BRF_FUNC_CALL_RET(checkDev(loc));
if (!internal)
{ BRF_FUNC_CALL_RET(checkCalStatus(loc)); }
return 0;
}
unsigned int waitCancel(const char* loc, const char* reason, String* error);
// Apply parameters from start notification message
unsigned int applyStartParams(const NamedList& params, String* error);
BrfInterface* m_owner; // The interface owning the device
String m_serial; // Serial number of device to use
bool m_initialized; // Initialized flag
bool m_exiting; // Exiting flag
bool m_closing; // Closing flag
bool m_closingDevice; // Closing device flag
bool m_notifyOff; // Notify power off
Mutex m_dbgMutex;
// libusb
libusb_context* m_context; // libusb context
libusb_device** m_list; // List of devices
unsigned int m_listCount; // Device list length
libusb_device_handle* m_devHandle; // Device handle
libusb_device* m_dev; // Pointer to used device (in m_list)
// Device info
RadioCapability m_radioCaps;
int m_devBus; // Device bus
int m_devAddr; // Device address
int m_devSpeed; // Device speed
String m_address; // Device address
String m_devSerial; // Device serial number
String m_devFwVerStr; // Device firmware version string
String m_devFpgaVerStr; // Device FPGA version string
String m_devFpgaFile; // Device FPGA file (nul if not loaded)
String m_devFpgaMD5; // FPGA MD5
String m_lmsVersion; // LMS chip version
uint16_t m_ctrlTransferPage; // Control transfer page size
DataBlock m_calCache;
//
unsigned int m_syncTout; // Sync transfer timeout (in milliseconds)
Semaphore m_syncSemaphore;
unsigned int m_ctrlTout; // Control transfer timeout (in milliseconds)
unsigned int m_bulkTout; // Bulk transfer timeout (in milliseconds)
int m_altSetting;
int m_rxShowDcInfo; // Output Rx DC info
int m_rxDcOffsetMax; // Rx DC offset correction
int m_rxDcAvgI; // Current average for I (in-phase) DC RX offset
int m_rxDcAvgQ; // Current average for Q (quadrature) DC RX offset
float m_freqOffset; // Master clock frequency adjustment
bool m_txGainCorrSoftware; // Use software TX GAIN correction
BrfDevIO m_txIO;
BrfDevIO m_rxIO;
DataBlock m_bufThres; // Thresholds for used buffers adjustment (BrfBufsThreshold)
LusbTransfer m_usbTransfer[EpCount]; // List of USB transfers
BrfDevState m_state; // State data used for current operation and backup / restore
bool m_syncTxStateSet; //
unsigned int m_syncTxStateCode; //
String m_syncTxStateError; //
BrfDevState m_syncTxState; // Data used to sync set state in send method
uint64_t m_rxTimestamp; // RX timestamp
uint64_t m_rxResyncCandidate; // RX: timestamp resync value
unsigned int m_rxTsPastIntervalMs; // RX: allowed timestamp in the past interval in 1 read operation
unsigned int m_rxTsPastSamples; // RX: How many samples in the past to allow in 1 read operation
float m_warnClamped; // TX: Warn clamped threshold (percent)
unsigned int m_minBufsSend; // Minimum buffers to send
unsigned int m_silenceTimeMs; // Silence timestamp related debug messages (in millseconds)
uint64_t m_silenceTs; // Silence timestamp related debug messages
// TX power scale
float m_txPowerBalance;
bool m_txPowerBalanceChanged;
float m_txPowerScaleI;
float m_txPowerScaleQ;
float m_wrPowerScaleI;
float m_wrPowerScaleQ;
int16_t m_wrMaxI;
int16_t m_wrMaxQ;
// amp linearization
float m_gainExpBreak; // amp linearization gain exapansion breakpoint, dB power scale
float m_gainExpSlope; // amp linearization gain exapansion slope, dB power gain
float m_phaseExpBreak; // amp linearization phase expansion breakpoint, dB power scale
float m_phaseExpSlope; // amp linearization phase expanaion slope, radians per power unit
// The parameters above are used to generate this table
long m_ampTable[2*2*2048]; // amp linearization table, complex pairs indexed in normalized power units
bool m_ampTableUse;
// Alter data
NamedList m_rxAlterDataParams;
bool m_rxAlterData;
int16_t m_rxAlterIncrement;
String m_rxAlterTsJumpPatern; // Pattern used to alter rx timestamps (kept to check changes)
bool m_rxAlterTsJumpSingle; // Stop altering ts after first pass
DataBlock m_rxAlterTsJump; // Values to alter rx timestamps
unsigned int m_rxAlterTsJumpPos; // Current position in rx alter timestamps
bool m_txPatternChanged;
ComplexVector m_txPattern;
ComplexVector m_txPatternBuffer;
unsigned int m_txPatternBufPos;
// Check & calibration
bool m_calLms; // Run LMS auto cal
int m_calibrateStatus;
int m_calibrateStop;
NamedList m_calibration; // Calibration parameters
String m_devCheckFile;
String m_bbCalDcFile;
String m_bbCalImbalanceFile;
BrfThread* m_calThread;
BrfThread* m_sendThread;
BrfThread* m_recvThread;
Semaphore m_internalIoSemaphore;
uint64_t m_internalIoTimestamp;
unsigned int m_internalIoTxRate;
unsigned int m_internalIoRxRate;
bool m_internalIoRateChanged;
Mutex m_threadMutex;
};
// Initialize data used to wait for interface Tx busy
// Clear the flag when destroyed
class BrfSerialize
{
public:
inline BrfSerialize(BrfLibUsbDevice* dev, bool tx, bool waitNow = true)
: status(0), m_device(dev), m_io(m_device->getIO(tx)), m_lock(0) {
if (waitNow)
wait();
}
inline ~BrfSerialize()
{ drop(); }
inline void drop()
{ m_lock.drop(); }
inline bool devLocked() const
{ return m_io.mutex.locked(); }
inline unsigned int wait(String* error = 0, long maxwait = -1) {
if (m_lock.acquire(m_io.mutex,maxwait)) {
if ((status = m_device->cancelled(error)) != 0)
drop();
}
else
status = m_device->showError(RadioInterface::Failure,
"Failed to serialize",brfDir(m_io.tx()),error,DebugWarn);
return status;
}
unsigned int status;
protected:
BrfLibUsbDevice* m_device;
BrfDevIO& m_io;
Lock m_lock;
};
class BrfInterface : public RadioInterface
{
YCLASS(BrfInterface,RadioInterface)
friend class BrfModule;
public:
~BrfInterface();
inline BrfLibUsbDevice* device() const
{ return m_dev; }
inline bool isDevice(void* dev) const
{ return m_dev && m_dev == (BrfLibUsbDevice*)dev; }
// Module reload
inline void reLoad() {
if (m_dev)
m_dev->reLoad();
}
inline void setPending(unsigned int oper, unsigned int code = Pending)
{ RadioInterface::setPending(oper,code); }
void notifyError(unsigned int status, const char* str, const char* oper);
virtual unsigned int initialize(const NamedList& params = NamedList::empty());
virtual unsigned int setParams(NamedList& params, bool shareFate = true);
virtual unsigned int setDataDump(int dir = 0, int level = 0,
const NamedList* params = 0) {
if (!m_dev)
return Failure;
m_dev->setDataDump(dir,level,params);
return 0;
}
virtual unsigned int send(uint64_t when, float* samples, unsigned size,
float* powerScale = 0);
virtual unsigned int recv(uint64_t& when, float* samples, unsigned& size);
unsigned int setFrequency(uint64_t hz, bool tx);
unsigned int getFrequency(uint64_t& hz, bool tx) const;
virtual unsigned int setTxFreq(uint64_t hz)
{ return setFrequency(hz,true); }
virtual unsigned int getTxFreq(uint64_t& hz) const
{ return getFrequency(hz,true); }
virtual unsigned int setRxFreq(uint64_t hz)
{ return setFrequency(hz,false); }
virtual unsigned int getRxFreq(uint64_t& hz) const
{ return getFrequency(hz,false); }
virtual unsigned int setFreqOffset(float offs, float* newVal = 0)
{ return m_dev->setFreqOffset(offs,newVal); }
virtual unsigned int setSampleRate(uint64_t hz);
virtual unsigned int getSampleRate(uint64_t& hz) const;
virtual unsigned int setFilter(uint64_t hz);
virtual unsigned int getFilterWidth(uint64_t& hz) const;
unsigned int setRxGain(int val, unsigned port, bool preMixer);
virtual unsigned int setRxGain1(int val, unsigned port)
{ return setRxGain(val,port,true); }
virtual unsigned int setRxGain2(int val, unsigned port)
{ return setRxGain(val,port,false); }
unsigned int setTxGain(int val, unsigned port, bool preMixer);
virtual unsigned int setTxGain1(int val, unsigned port)
{ return setTxGain(val,port,true); }
virtual unsigned int setTxGain2(int val, unsigned port)
{ return setTxGain(val,port,false); }
virtual unsigned int getTxTime(uint64_t& time) const
{ return m_dev->getTimestamp(true,time); }
virtual unsigned int getRxTime(uint64_t& time) const
{ return m_dev->getTimestamp(false,time); }
virtual unsigned int setTxPower(const unsigned dBm)
{ return setTxGain2(dBm,0); }
virtual unsigned int setPorts(unsigned ports) {
if (ports == m_radioCaps->currPorts)
return 0;
return ports ? NotSupported : OutOfRange;
}
// Set pre and post mixer value
unsigned int setGain(bool tx, int val, unsigned int port,
int* newValue = 0) const;
virtual unsigned status(int port = -1) const
{ return (m_totalErr & FatalErrorMask); }
virtual unsigned int setLoopback(const char* name = 0)
{ return m_dev ? m_dev->setLoopback(name) : NotInitialized; }
virtual unsigned int calibrate()
{ return m_dev->calibrate(); }
virtual void completeDevInfo(NamedList& p, bool full = false, bool retData = false);
protected:
BrfInterface(const char* name);
// Method to call after creation to init the interface
unsigned int init(const NamedList& params, String& error);
virtual void destroyed();
private:
BrfLibUsbDevice* m_dev; // Used device
};
class BrfThread : public Thread
{
public:
enum Type {
Unknown = 0,
DevCalibrate,
DevSend,
DevRecv,
};
// Device thread
BrfThread(BrfLibUsbDevice* dev, int type, const NamedList& p = NamedList::empty(),
const char* name = 0, Thread::Priority prio = Thread::Normal);
~BrfThread()
{ notify(); }
inline const char* name() const
{ return m_params; }
inline BrfInterface* ifc() const
{ return (m_device ? m_device->owner() : 0); }
virtual void cleanup()
{ notify(); }
inline bool isPaused() const
{ return m_paused; }
// I/O pause check. Update timestamp on resume
// This method is expected to be called from run()
inline bool paused(bool tx, uint64_t& ts, unsigned int& status) {
if (!m_pauseToggle)
return m_paused;
m_paused = !m_paused;
if (m_paused)
status = 0;
else if (m_device)
status = m_device->getTimestamp(tx,ts);
else
status = RadioInterface::NotInitialized;
bool failed = (status && status != RadioInterface::Cancelled);
Debug(ifc(),failed ? DebugNote : DebugAll,"%s %s at ts=" FMT64U " [%p]",
name(),(m_paused ? "paused" : "resume"),ts,ifc());
m_pauseToggle = false;
return m_paused;
}
// Start this thread. Delete object on failure and return 0
BrfThread* start();
// Pause / resume a thread
static inline unsigned int pauseToggle(BrfThread*& th, Mutex* mtx, bool on,
String* error = 0);
static inline unsigned int pause(BrfThread*& th, Mutex* mtx, String* error = 0)
{ return pauseToggle(th,mtx,true,error); }
static inline unsigned int resume(BrfThread*& th, Mutex* mtx, String* error = 0)
{ return pauseToggle(th,mtx,false,error); }
// Stop thread
static void cancelThread(BrfThread*& th, Mutex* mtx, unsigned int waitMs,
DebugEnabler* dbg, void* ptr);
protected:
virtual void run();
void notify();
int m_type;
NamedList m_params;
BrfLibUsbDevice* m_device;
bool m_paused;
bool m_pauseToggle;
const char* m_priority;
};
class BrfModule : public Module
{
friend class BrfInterface;
public:
enum Relay {
RadioCreate = Private,
};
BrfModule();
~BrfModule();
bool findIfaceByDevice(RefPointer<BrfInterface>& iface, void* dev);
inline bool findIface(RefPointer<BrfInterface>& iface, const String& n) {
Lock lck(this);
ObjList* o = m_ifaces.find(n);
if (o)
iface = static_cast<BrfInterface*>(o->get());
return iface != 0;
}
protected:
virtual void initialize();
virtual bool received(Message& msg, int id);
virtual void statusModule(String& str);
virtual void statusParams(String& str);
virtual void statusDetail(String& str);
virtual bool commandComplete(Message& msg, const String& partLine,
const String& partWord);
bool createIface(NamedList& params);
void completeIfaces(String& dest, const String& partWord);
bool onCmdControl(BrfInterface* ifc, Message& msg);
bool onCmdStatus(String& retVal, String& line);
bool onCmdGain(BrfInterface* ifc, Message& msg, int tx = -1, bool preMixer = true);
bool onCmdCorrection(BrfInterface* ifc, Message& msg, int tx = -1, int corr = 0);
bool onCmdLmsWrite(BrfInterface* ifc, Message& msg);
bool onCmdBufOutput(BrfInterface* ifc, Message& msg);
bool onCmdShow(BrfInterface* ifc, Message& msg, const String& what = String::empty());
bool onCmdFreqOffs(BrfInterface* ifc, Message& msg);
bool onCmdFreqCal(BrfInterface* ifc, Message& msg, bool start = true);
void setDebugPeripheral(const NamedList& list);
void setSampleEnergize(const String& value);
inline bool waitDisciplineFree() {
do {
Lock lck(this);
if (!m_disciplineBusy) {
m_disciplineBusy = true;
return true;
}
lck.drop();
Thread::idle();
}
while (!Thread::check(false));
return false;
}
unsigned int m_ifaceId;
ObjList m_ifaces;
bool m_disciplineBusy; ///< flag used to serialize the VCTCXO discipliner
unsigned m_lastDiscipline; ///< the timestamp (sec) of the last call on the discipliner
};
static bool s_usbContextInit = false; // USB library init flag
const float BrfVctcxoDiscipliner::s_ppbPerUnit = 19000 * 1.25 / 256;
INIT_PLUGIN(BrfModule);
static Configuration s_cfg; // Configuration file (protected by plugin mutex)
static const String s_modCmds[] = {"help",""};
static const String s_ifcCmds[] = {
"txgain1", "txgain2", "rxgain1", "rxgain2",
"txdci", "txdcq", "txfpgaphase", "txfpgagain",
"rxdci", "rxdcq", "rxfpgaphase", "rxfpgagain",
"showstatus", "showboardstatus", "showstatistics", "showtimestamps", "showlms",
"vgagain","correction","lmswrite",
"bufoutput","rxdcoutput","txpattern","show",
"cal_stop", "cal_abort",
"balance",
"gainexp", "phaseexp",
"freqoffs", "freqcalstart", "freqcalstop",
""};
// libusb
static unsigned int s_lusbSyncTransferTout = LUSB_SYNC_TIMEOUT; // Sync transfer timeout def val (in milliseconds)
static unsigned int s_lusbCtrlTransferTout = LUSB_CTRL_TIMEOUT; // Control transfer timeout def val (in milliseconds)
static unsigned int s_lusbBulkTransferTout = LUSB_BULK_TIMEOUT; // Bulk transfer timeout def val (in milliseconds)
static BrfPeripheral s_uartDev[BrfLibUsbDevice::UartDevCount] = {
BrfPeripheral("GPIO",0x00),
BrfPeripheral("LMS",0x10),
BrfPeripheral("VCTCXO",0x20),
BrfPeripheral("SI5338",0x30)
};
static const TokenDict s_usbEndpoint[] = {
{"SEND_SAMPLES", BrfLibUsbDevice::EpSendSamples},
{"SEND_CTRL", BrfLibUsbDevice::EpSendCtrl},
{"READ_SAMPLES", BrfLibUsbDevice::EpReadSamples},
{"READ-CTRL", BrfLibUsbDevice::EpReadCtrl},
{0,0}
};
static const TokenDict s_loopback[] = {
{"firmware", BrfLibUsbDevice::LoopFirmware},
{"lpf-to-rxout", BrfLibUsbDevice::LoopLpfToRxOut},
{"lpf-to-vga2", BrfLibUsbDevice::LoopLpfToVga2},
{"vga1-to-vga2", BrfLibUsbDevice::LoopVga1ToVga2},
{"lpf-to-lpf", BrfLibUsbDevice::LoopLpfToLpf},
{"vga1-to-lpf", BrfLibUsbDevice::LoopVga1ToLpf},
{"pa-to-lna1", BrfLibUsbDevice::LoopRfLna1},
{"pa-to-lna2", BrfLibUsbDevice::LoopRfLna2},
{"pa-to-lna3", BrfLibUsbDevice::LoopRfLna3},
{"none", BrfLibUsbDevice::LoopNone},
{0,0}
};
static const TokenDict s_pa[] = {
{"AUXPA", BrfLibUsbDevice::LmsPaAux},
{"PA1", BrfLibUsbDevice::LmsPa1},
{"PA2", BrfLibUsbDevice::LmsPa2},
{0,0}
};
static const TokenDict s_lpf[] = {
{"disabled", BrfLibUsbDevice::LpfDisabled},
{"bypassed", BrfLibUsbDevice::LpfBypass},
{"normal", BrfLibUsbDevice::LpfNormal},
{0,0}
};
static const TokenDict s_lnaGain[] = {
{"BYPASS", BrfLibUsbDevice::LnaGainBypass},
{"MID", BrfLibUsbDevice::LnaGainMid},
{"MAX", BrfLibUsbDevice::LnaGainMax},
{"Unhandled", BrfLibUsbDevice::LnaGainUnhandled},
{0,0}
};
static const TokenDict s_corr[] = {
{"I", BrfLibUsbDevice::CorrLmsI},
{"Q", BrfLibUsbDevice::CorrLmsQ},
{"PHASE", BrfLibUsbDevice::CorrFpgaPhase},
{"GAIN", BrfLibUsbDevice::CorrFpgaGain},
{0,0}
};
static bool completeStrList(String& dest, const String& partWord, const String* ptr)
{
if (!ptr)
return false;
while (*ptr)
Module::itemComplete(dest,*ptr++,partWord);
return false;
}
static inline void loadCfg(Configuration* cfg = 0, bool warn = true)
{
if (!cfg)
cfg = &s_cfg;
*cfg = Engine::configFile("ybladerf");
cfg->load(warn);
}
static void lusbSetDebugLevel(int level = -1)
{
// No lock needed: this function is called from plugin init (loads the config) or
// context init (locks the plugin)
if (!s_usbContextInit)
return;
if (level < 0) {
String* l = s_cfg.getKey(YSTRING("libusb"),YSTRING("debug_level"));
::libusb_set_debug(0,l ? l->toInteger(0,0,0) : 0);
}
else
::libusb_set_debug(0,level);
}
// libusb transfer stream callback
static void lusbTransferCb(libusb_transfer* transfer)
{
if (!transfer) {
DDebug(&__plugin,DebugWarn,"lusbTransferCb() called with NULL transfer");
return;
}
LusbTransfer* t = (LusbTransfer*)(transfer->user_data);
#ifdef DEBUG_LUSB_TRANSFER_CALLBACK
int level = DebugAll;
if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
level = DebugNote;
if (!__plugin.debugAt(level))
return;
RefPointer<BrfInterface> ifc;
String ifcInfo;
if (t && __plugin.findIfaceByDevice(ifc,t->device)) {
ifcInfo.printf("(%p %s)",(BrfInterface*)ifc,ifc->debugName());
ifc = 0;
}
String x;
String tmp;
x << "\r\ninterface=" << ifcInfo.safe("not found");
x << tmp.printf("\r\nhandle=%p",transfer->dev_handle);
x << tmp.printf("\r\nuser_data=%p",transfer->user_data);
x << tmp.printf("\r\nflags=0x%x",transfer->flags);
x << "\r\ntype=";
switch (transfer->type) {
case LIBUSB_TRANSFER_TYPE_CONTROL:
x << "CONTROL";
break;
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
x << "ISOCHRONOUS";
break;
case LIBUSB_TRANSFER_TYPE_BULK:
x << "BULK";
break;
case LIBUSB_TRANSFER_TYPE_INTERRUPT:
x << "INTERRUPT";
break;
//case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
// x << "STREAM";
// break;
default:
x << (unsigned int)(transfer->type);
}
String endp;
switch (transfer->endpoint) {
case BRF_ENDP_TX_SAMPLES:
endp = lookup(BrfLibUsbDevice::EpSendSamples,s_usbEndpoint);
break;
case BRF_ENDP_TX_CTRL:
endp = lookup(BrfLibUsbDevice::EpSendCtrl,s_usbEndpoint);
break;
case BRF_ENDP_RX_SAMPLES:
endp = lookup(BrfLibUsbDevice::EpReadSamples,s_usbEndpoint);
break;
case BRF_ENDP_RX_CTRL:
endp = lookup(BrfLibUsbDevice::EpReadCtrl,s_usbEndpoint);
break;
default:
endp.printf("0x%x",transfer->endpoint);
}
x << "\r\nendpoint=" << endp;
x << "\r\ntimeout=" << transfer->timeout << "ms";
BrfLibUsbDevice::appendLusbError(x,transfer->status,"\r\nstatus=");
x << "\r\ncurrent_buffer_len=" << transfer->length;
x << "\r\ntransferred=" << transfer->actual_length;
Debug(&__plugin,level,"lusbTransferCb(%p)%s",transfer,encloseDashes(x));
#endif
if (!t)
return;
Lock lck(t);
if (transfer->status == LIBUSB_TRANSFER_COMPLETED &&
transfer->length != transfer->actual_length) {
t->status = RadioInterface::HardwareIOError;
t->error.printf("Incomplete transfer %u/%u",transfer->actual_length,transfer->length);
}
else
t->status = BrfLibUsbDevice::lusbCheckSuccess(transfer->status,&t->error);
t->running(false);
}
//
// BrfPeripheral
//
void BrfPeripheral::setTrack(bool tx, bool rx, const String& addr, int level)
{
bool changed = m_tx != tx || m_rx != rx;
String oldTrackAddr;
if (m_haveTrackAddr)
oldTrackAddr.hexify(m_trackAddr,sizeof(m_trackAddr));
m_tx = tx;
m_rx = rx;
m_trackLevel = level;
m_haveTrackAddr = false;
::memset(m_trackAddr,0,sizeof(m_trackAddr));
if ((m_tx || m_rx) && addr && addr != oldTrackAddr) {
DataBlock tmp;
if (tmp.unHexify(addr)) {
uint8_t* d = tmp.data(0);
for (unsigned int i = 0; i < tmp.length(); i++, d++) {
if (*d < 128) {
m_trackAddr[*d] = 1;
m_haveTrackAddr = true;
}
else
Debug(&__plugin,DebugConf,
"Ignoring invalid track address 0x%x for peripheral '%s'",*d,c_str());
}
}
else
Debug(&__plugin,DebugConf,
"Ignoring invalid track addresses for peripheral '%s'",c_str());
}
String newTrackAddr;
if (m_haveTrackAddr) {
newTrackAddr.hexify(m_trackAddr,sizeof(m_trackAddr));
changed = (newTrackAddr != oldTrackAddr);
}
else if (oldTrackAddr)
changed = true;
if (!changed)
return;
String state;
if (m_tx || m_rx) {
String ta;
if (m_haveTrackAddr) {
String tmp;
for (uint8_t i = 0; i < BRF_ARRAY_LEN(m_trackAddr); i++)
if (m_trackAddr[i])
ta.append(tmp.hexify(&i,1)," ");
}
Debug(&__plugin,DebugAll,
"%s peripheral debug changed: tx=%s rx=%s tracked_addr=%s level=%d",
c_str(),String::boolText(m_tx),String::boolText(m_rx),ta.safe(),level);
}
else
Debug(&__plugin,DebugAll,"%s peripheral debug is disabled",c_str());
}
//
// BrfBufsThreshold
//
const char* BrfBufsThreshold::init(DataBlock& db, const String& str,
const RadioCapability& caps)
{
db.clear();
if (!str)
return 0;
ObjList* list = str.split(',',false);
unsigned int n = list->count();
const char* result = 0;
if (!n) {
TelEngine::destruct(list);
return 0;
}
db.assign(0,n * sizeof(BrfBufsThreshold));
BrfBufsThreshold* t = (BrfBufsThreshold*)db.data(0);
unsigned int i = 0;
for (ObjList* o = list->skipNull(); o; o = o->skipNext(), ++i) {
String& s = *static_cast<String*>(o->get());
int pos1 = s.find('/');
if (pos1 < 0) {
result = "invalid format";
break;
}
int64_t sRate = s.substr(0,pos1).trimBlanks().toInt64();
int64_t bSamples = 0;
int txMinBufs = 0;
int pos2 = s.find('/',pos1 + 1);
if (pos2 > pos1) {
String tmp = s.substr(pos2 + 1).trimBlanks();
if (tmp)
txMinBufs = tmp.toInteger(-1);
bSamples = s.substr(pos1 + 1,pos2 - pos1 - 1).trimBlanks().toInt64(-1);
}
else
bSamples = s.substr(pos1 + 1).trimBlanks().toInt64(-1);
XDebug(&__plugin,DebugAll,"BrfBufsThreshold::init() %u/%u '%s' -> "FMT64"/"FMT64"/%d",
i + 1,n,s.c_str(),sRate,bSamples,txMinBufs);
if (sRate < caps.minSampleRate || sRate > caps.maxSampleRate)
result = "samplerate out of range";
else if (bSamples <= 0 || bSamples > 0xffffffff)
result = "invalid buffered_samples";
else if (txMinBufs < 0)
result = "invalid tx_min_buffers";
else {
t[i].sampleRate = sRate;
t[i].bufferedSamples = bSamples;
t[i].txMinBufs = txMinBufs;
if (!i || t[i].sampleRate > t[i - 1].sampleRate ||
t[i].bufferedSamples > t[i - 1].bufferedSamples)
continue;
result = "not in ascending order";
}
break;
}
TelEngine::destruct(list);
if (result) {
db.clear();
return result;
}
#ifdef XDEBUG
String s;
for (i = 0; i < n; i++, t++)
s << "\r\n" << t->sampleRate << "\t" << t->bufferedSamples << "\t" << t->txMinBufs;
Output("Got %u BrfBufsThreshold:%s",n,encloseDashes(s));
#endif
return 0;
}
BrfBufsThreshold* BrfBufsThreshold::findThres(DataBlock& db, unsigned int sampleRate)
{
if (!(db.length() && sampleRate))
return 0;
unsigned int n = db.length() / sizeof(BrfBufsThreshold);
BrfBufsThreshold* t = (BrfBufsThreshold*)db.data(0);
for (unsigned int i = 0; i < n; i++, t++) {
if (t->sampleRate <= sampleRate) {
// Last entry or less than next one: return it
if (i == n - 1 || sampleRate < t[1].sampleRate)
return t;
continue;
}
}
return 0;
}
//
// LusbTransfer
//
bool LusbTransfer::fillBulk(uint8_t* data, unsigned int len, unsigned int tout)
{
if (!alloc())
return false;
::libusb_fill_bulk_transfer(transfer,device->handle(),ep,data,len,lusbTransferCb,
this,tout);
return true;
}
bool LusbTransfer::submit()
{
status = BrfLibUsbDevice::lusbCheckSuccess(::libusb_submit_transfer(transfer),
&error,"libusb_submit_transfer() failed ");
return status == 0;
}
unsigned int LusbTransfer::cancel(String* error)
{
if (!transfer)
return 0;
int code = ::libusb_cancel_transfer(transfer);
if (code == LIBUSB_SUCCESS)
return 0;
m_running = false;
if (code == LIBUSB_ERROR_NOT_FOUND)
return 0;
return BrfLibUsbDevice::lusbCheckSuccess(code,error,
"libusb_cancel_transfer() failed ");
}
//
// BrfBbCalData
//
void BrfBbCalData::initCal(BrfLibUsbDevice& dev, bool dc, String& fName)
{
if (!fName)
fName = param(dc,"file_dump");
if (fName) {
replaceDumpParams(fName,0,true);
if (m_dump.init(m_params,fName)) {
if (m_dump.dumpHeader()) {
const String& fmt = param(dc,"header_format");
NamedString* ns = new NamedString("data");
dev.dumpState(*ns,m_params,true,true);
*ns <<
"\r\n\r\nOmega_Error: " << omega(true) <<
"\r\nOmega_Test: " << omega(false);
String* s = new String(fmt.safe("TIME: ${time}${newline}${data}"));
replaceDumpParams(*s,ns);
m_dump.append(s);
}
m_dump.dumpDataFmt(calTone(),m_params,"dump_filter_cal");
m_dump.dumpDataFmt(testTone(),m_params,"dump_filter_test");
}
}
else
m_dump.writeData(true);
unsigned int n = uintParam(dc,"dump_tone");
if (n) {
String cS, tS;
if (n > calTone().length())
n = calTone().length();
calTone().head(n).dump(cS,Math::dumpComplex," ","%.2f,%.2f");
testTone().head(n).dump(tS,Math::dumpComplex," ","%.2f,%.2f");
Output("Omega cal=%f test=%f\r\nCAL: %s\r\nTEST: %s",omega(true),omega(false),
cS.safe(),tS.safe());
}
}
void BrfBbCalData::finalizeCal(const String& result)
{
if (m_dump.valid()) {
const String& fmt = m_params[YSTRING("dump_result_format")];
if (fmt) {
NamedString* ns = new NamedString("data",result.safe("FAILURE"));
m_dump.append(replaceDumpParamsFmt(fmt,ns));
}
}
}
void BrfBbCalData::dumpCorrStart(unsigned int pass, int corr, int corrVal, int fixedCorr,
int fixedCorrVal, unsigned int range, unsigned int step,
int calValMin, int calValMax)
{
const String& fmt = m_params[YSTRING("dump_pass_info_start")];
if (fmt) {
String* s = 0;
if (fmt != YSTRING("-"))
s = new String(fmt);
else
s = new String("${newline}${newline}${data}");
NamedString* ns = new NamedString("data");
ns->printf(1024,"Pass #%u calibrating %s (crt: %d) %s=%d "
"samples=%u range=%d step=%d interval=[%d..%d]",
pass,lookup(corr,s_corr),corrVal,lookup(fixedCorr,s_corr),fixedCorrVal,
samples(),range,step,calValMin,calValMax);
replaceDumpParams(*s,ns);
m_dump.append(s);
}
unsigned int n = 0;
if (m_params[YSTRING("dump_accumulate_format")])
n = range * 2 + 1;
m_calAccum.reset(n);
m_testAccum.reset(n);
m_totalAccum.reset(n);
}
void BrfBbCalData::dumpCorrEnd(bool dc)
{
if (m_calAccum.data.length()) {
const String& accum = m_params[YSTRING("dump_accumulate_format")];
if (accum) {
m_calAccum.normalize();
m_testAccum.normalize();
m_totalAccum.normalize();
String* s = new String(accum);
replaceDumpParams(*s,dumpNsData(m_calAccum.data,"data_cal"),false,
dumpNsData(m_testAccum.data,"data_test"),
dumpNsData(m_totalAccum.data,"data_total"));
m_dump.append(s);
}
}
const String& fmt = m_params[YSTRING("dump_pass_info_end")];
if (fmt) {
String* s = 0;
if (fmt != YSTRING("-"))
s = new String(fmt);
else
s = new String("${newline}${data}");
NamedString* ns = new NamedString("data");
ns->printf(1024,"Result: %d/%d Min/Max: cal=%f/%f test=%f/%f total=%f/%f",
(dc ? m_dcI : m_phase),(dc ? m_dcQ : m_gain),m_cal.min,m_cal.max,
m_test.min,m_test.max,m_total.min,m_total.max);
replaceDumpParams(*s,ns);
m_dump.append(s);
}
}
//
// BrfDevTmpAltSet
// Temporary change alt setting. Restore on destruction
//
unsigned int BrfDevTmpAltSet::set(int altSet, String* error, const char* oper)
{
restore();
if (!m_device || m_device->m_altSetting == altSet)
return 0;
unsigned int status = m_device->lusbSetAltInterface(altSet,error);
if (status)
return status;
m_oper = oper;
m_tmpAltSet = altSet;
DDebug(m_device->owner(),DebugAll,
"Temporary changed alt interface to %s for '%s' [%p]",
altSetName(m_tmpAltSet),m_oper,m_device->owner());
return 0;
}
unsigned int BrfDevTmpAltSet::restore()
{
if (m_tmpAltSet == BRF_ALTSET_INVALID)
return 0;
String e;
unsigned int status = m_device->lusbSetAltInterface(m_tmpAltSet,&e);
if (status == 0)
DDebug(m_device->owner(),DebugAll,
"Restored alt interface to %s after '%s' [%p]",
altSetName(m_tmpAltSet),m_oper,m_device->owner());
else
Debug(m_device->owner(),DebugCrit,
"Failed to restore alt interface after '%s': %s [%p]",
m_oper,e.c_str(),m_device->owner());
m_tmpAltSet = BRF_ALTSET_INVALID;
return status;
}
//
// BrfVctcxoDiscipliner
//
bool BrfVctcxoDiscipliner::onCmdFreqCal(Message& msg, bool start)
{
if (start) {
if (!m_trimsLeft) {
Debug(dev().owner(),DebugNote,"Frequency calibration is starting [%p]",dev().owner());
m_trimsLeft = -1;
}
const NamedString* s = msg.getParam(YSTRING("system_accuracy"));
if (s) {
int us = s->toInteger(-1,0,0,2000);
if (us >= 0) {
if (us != (int)m_systemAccuracy) {
postponeActivity(1,true);
m_systemAccuracy = us;
scheduleNextPinning(m_delay);
}
}
else
Debug(dev().owner(),DebugNote,"VCTCXO discipliner: ignoring invalid %s='%s' [%p]",
s->name().c_str(),s->c_str(),dev().owner());
}
s = msg.getParam(YSTRING("count"));
if (s) {
int count = s->toInteger();
if (count >= 0)
m_trimsLeft = (count) ? count : -1;
else
Debug(dev().owner(),DebugNote,"VCTCXO discipliner: ignoring invalid %s='%s' [%p]",
s->name().c_str(),s->c_str(),dev().owner());
}
}
else if (!m_trimsLeft) {
msg.retValue() << "frequency calibration is currently disabled";
return true;
}
// return current parameters
if (m_trimsLeft > 0)
msg.retValue() << "count=" << m_trimsLeft << " ";
uint64_t usec = Time::now();
unsigned int last = (!m_samples ? 0 : usToMin(m_nextPinning - m_timestamp));
unsigned int remains = (!m_samples ? 0 : usToMin(m_nextPinning - usec));
msg.retValue() << "measurement_interval=" << last << "min (" << remains <<
"min left) system_accuracy=" << m_systemAccuracy << "us measurement_accuracy=" <<
m_accuracyPpb << "ppb freqoffs=" << dev().m_freqOffset;
if (m_resumePoint > usec)
msg.retValue() << " (idling for " << usToMin(m_resumePoint - usec) << "min)";
else if (!start && m_samples) {
uint64_t samples = 0, timestamp = 0;
uint16_t delay = 0;
int ppb = measureDrift(samples,timestamp,delay);
if (samples) {
String str;
msg.retValue() << (str.printf(" (current drift: ppb=%d interval=%gmin delay=%uus",
ppb,(timestamp - m_timestamp) / 60.0e6F,delay));
}
else
msg.retValue() << " (drift measurement failed)";
}
return true;
}
void BrfVctcxoDiscipliner::postponeActivity(unsigned minutes, bool dropData)
{
if (minutes) {
m_resumePoint = usToMin(minutes) + Time::now();
if (m_trace)
Debug(dev().owner(),DebugInfo,"VCTCXO discipliner: postpone %u min [%p]",minutes,dev().owner());
}
if (dropData && m_samples) {
m_samples = 0;
if (m_trace)
Debug(dev().owner(),DebugInfo,"VCTCXO discipliner: dropping current data [%p]",dev().owner());
}
}
void BrfVctcxoDiscipliner::disableDiscipline(bool onCmd)
{
if (!m_trimsLeft)
return;
m_trimsLeft = 0;
postponeActivity(0,true);
Debug(dev().owner(),DebugNote,"Frequency calibration is stopping (%s) [%p]",
onCmd ? "changed by command" : "disabled",dev().owner());
if (onCmd)
dev().notifyFreqOffs();
}
void BrfVctcxoDiscipliner::trimVctcxo(uint64_t timestamp, int drift)
{
// process a previously measured drift or as forced input
if (processData(drift ? drift : m_driftPpb))
return;
// minimize activity until all prerequisites are met
if (!m_trimsLeft || outdatedConfig() || m_resumePoint > timestamp || init())
return;
// Dump delays ?
if (m_dumpDelays) {
uint64_t samples = 0;
uint64_t timestamp = 0;
uint16_t delay = 0;
String err;
Thread::yield();
dev().samplesAndTimestamp(samples,timestamp,delay,&err);
if (samples) {
bool dump = (m_dumpDelays == 1);
m_dumpDelays--;
m_delayStat.append(String(delay)," ");
if (dump) {
Output("VCTCXO discipliner delays: %s",m_delayStat.c_str());
m_delayStat.clear();
}
}
}
// wait the passing of the baseline interval before trying to determine the current drift
if (m_nextPinning > timestamp)
return;
uint64_t samples = 0;
uint16_t delay = 0;
m_driftPpb = measureDrift(samples,timestamp,delay);
// update the baseline interval if the measurement is valid
if (!samples)
return;
scheduleNextPinning(delay);
// drop the measured drift if the measurement isn't accurate
if (m_nextPinning > timestamp) {
if (m_trace)
Debug(dev().owner(),DebugInfo,
"VCTCXO discipliner: inaccurate measurement rescheduled in %umin [%p]",
usToMin(m_nextPinning - Time::now()),dev().owner());
m_driftPpb = 0;
return;
}
// replace the initial measurement
m_samples = samples;
m_timestamp = timestamp;
m_delay = delay;
}
void BrfVctcxoDiscipliner::scheduleNextPinning(uint16_t delay)
{
m_nextPinning = m_systemAccuracy;
if (m_delay > m_knownDelay)
m_nextPinning += m_delay - m_knownDelay;
if (delay > m_knownDelay)
m_nextPinning += delay - m_knownDelay;
m_nextPinning *= 1000000000UL / m_accuracyPpb;
m_nextPinning += m_timestamp;
if (m_trace)
Debug(dev().owner(),DebugInfo,
"VCTCXO discipliner: scheduled next pinning at %f (%umin) system_accuracy=%u "
"accuracy_ppb=%u delay(initial/current/known)=%u/%u/%u [%p]",
1.0e-6 * m_nextPinning,usToMin(m_nextPinning - m_timestamp),
m_systemAccuracy,m_accuracyPpb,m_delay,delay,m_knownDelay,dev().owner());
}
bool BrfVctcxoDiscipliner::outdatedConfig()
{
// check if current configuration is already valid
if (dev().getDirState(true).rfEnabled
&& dev().m_calibrateStatus != BrfLibUsbDevice::Calibrating
&& dev().m_freqOffset == m_freqOffset
&& dev().getDirState(true).sampleRate == m_confSampleRate
&& m_confSampleRate)
return false;
if (m_freqOffset != dev().m_freqOffset) {
if (m_trace && m_freqOffset)
Debug(dev().owner(),DebugInfo,
"VCTCXO discipliner: voltageDAC changed %g -> %g [%p]",
m_freqOffset,dev().m_freqOffset,dev().owner());
m_freqOffset = dev().m_freqOffset;
}
if (m_confSampleRate != dev().getDirState(true).sampleRate) {
if (m_trace && m_confSampleRate)
Debug(dev().owner(),DebugInfo,
"VCTCXO discipliner: configSampleRate changed %u -> %u [%p]",
m_confSampleRate,dev().getDirState(true).sampleRate,dev().owner());
m_confSampleRate = dev().getDirState(true).sampleRate;
}
postponeActivity(3,true);
return true;
}
bool BrfVctcxoDiscipliner::init()
{
if (!m_samples) {
samplesAndTimestamp(m_samples,m_timestamp,m_delay,20);
scheduleNextPinning(m_delay);
return true;
}
return false;
}
bool BrfVctcxoDiscipliner::processData(int drift)
{
if (!drift)
return false;
if (m_driftPpb && drift != m_driftPpb) {
Debug(dev().owner(),DebugNote,
"VCTCXO discipliner: dropping last measured drift %dppb [%p]",
m_driftPpb,dev().owner());
m_driftPpb = 0;
}
// transform the drift in voltageDAC units used for trimming the VCTCXO
float trimDAC = -drift / s_ppbPerUnit;
// limit the change in voltage (trimDAC = +/-10 => approx. +/-0.1V)
const int limit = 12; // arbitrary
if (trimDAC < -limit || trimDAC > limit) // clamp
trimDAC = (trimDAC > limit) ? limit : -limit;
float newOffs = dev().m_freqOffset + trimDAC;
if (m_trace)
Debug(dev().owner(),(!m_driftPpb) ? DebugInfo : DebugNote,
"VCTCXO discipliner: changing FrequencyOffset %g -> %g drift=%dppb [%p]",
dev().m_freqOffset,newOffs,drift,dev().owner());
// trim the VCTCXO
unsigned status = dev().setFreqOffset(newOffs,0,false);
if (status) {
// postpone activity for a minute to avoid a flood of debug messages
postponeActivity(1);
XDebug(dev().owner(),DebugNote,
"VCTCXO discipliner: failed to set FrequencyOffset to %g status=%u %s [%p]",
newOffs,status,RadioInterface::errorName(status),dev().owner());
return true;
}
postponeActivity(1,true);
// no more actions to be done if this was a forced drift correction
if (!m_driftPpb)
return true;
// enqueue a feedback message with the adjusted frequency offset
dev().notifyFreqOffs();
// clear the pending drift
m_driftPpb = 0;
// decrease the number of scheduled trims, unless toggled on (-1)
if (m_trimsLeft > 0) {
m_trimsLeft--;
if (!m_trimsLeft)
Debug(dev().owner(),DebugNote,
"Frequency calibration is stopping (count=0) [%p]",dev().owner());
else if (m_trace)
Debug(dev().owner(),DebugInfo,"VCTCXO discipliner: %d trims left [%p]",
m_trimsLeft,dev().owner());
}
return true;
}
int BrfVctcxoDiscipliner::measureDrift(uint64_t& samples, uint64_t& timestamp, uint16_t& delay)
{
samplesAndTimestamp(samples,timestamp,delay);
// revoke the measurement results for invalid samples or timestamp
if (samples < m_samples || timestamp < m_timestamp)
samples = 0;
if (!samples) {
XDebug(dev().owner(),DebugInfo,"VCTCXO discipliner: invalid sample to timestamp pinning,"
" failed to measure drift [%p]",dev().owner());
return 0;
}
// compute the average sample rate for the current interval,
// expressed in Hz (the microsec timestamp gets converted to sec)
double sampleRate = (double)(samples - m_samples) / (1.0e-6 * (timestamp - m_timestamp));
int drift = 1.0e9 * (sampleRate / m_confSampleRate - 1);
if (m_trace)
Debug(dev().owner(),DebugInfo,"VCTCXO discipliner: measured drift=%dppb sampleRate "
"current=%f configured=%u deltaSamples=" FMT64U " deltaTs=" FMT64U " [%p]",
drift,sampleRate,m_confSampleRate,samples - m_samples,timestamp - m_timestamp,dev().owner());
return drift;
}
void BrfVctcxoDiscipliner::samplesAndTimestamp(uint64_t& samples, uint64_t& timestamp,
uint16_t& delay, unsigned maxIter)
{
static unsigned int s_stop = RadioInterface::NotInitialized | RadioInterface::NotCalibrated |
RadioInterface::Cancelled;
samples = 0;
delay = m_maxDelay + 1;
unsigned timeouts = 0;
unsigned i = 0;
for (; i < maxIter; i++) {
uint64_t tempSamples = 0;
uint64_t tempTs = 0;
uint16_t tempDelay = 0;
String serializeErr;
Thread::yield();
unsigned status = dev().samplesAndTimestamp(tempSamples,tempTs,tempDelay,&serializeErr);
if (status) {
if (0 != (status & s_stop)) {
postponeActivity(1);
return;
}
if (status == RadioInterface::Failure && serializeErr)
timeouts++;
else if (status & RadioInterface::FatalErrorMask) {
disableDiscipline();
return;
}
}
// drop invalid and imprecise measurements
if (!tempSamples || tempDelay > delay)
continue;
// higher accuracy measurement
delay = tempDelay;
samples = tempSamples;
timestamp = tempTs;
if (delay < m_knownDelay) {
if (m_trace)
Debug(dev().owner(),DebugInfo,"VCTCXO discipliner: known delay changed %u -> %u [%p]",
m_knownDelay,delay * 19 / 20,dev().owner());
m_knownDelay = delay * 19 / 20;
scheduleNextPinning(m_delay);
}
// optimal measurement
if (delay < m_bestDelay)
break;
}
if (m_trace)
Debug(dev().owner(),(delay < m_maxDelay) ? DebugInfo : DebugNote,
"VCTCXO discipliner: got samples=" FMT64U " timestamp=%f delay=%u "
"(max=%u best=%u known=%u) iteration %u/%u timeouts=%u [%p]",
samples,1.0e-6 * timestamp,delay,m_maxDelay,m_bestDelay,
m_knownDelay,i,maxIter,timeouts,dev().owner());
}
//
// BrfLibUsbDevice
//
#define BRF_TX_SERIALIZE_(waitNow,instr) \
BrfSerialize txSerialize(this,true,waitNow); \
if (txSerialize.status) \
instr
#define BRF_TX_SERIALIZE BRF_TX_SERIALIZE_(true,return txSerialize.status)
#define BRF_TX_SERIALIZE_CHECK_DEV(loc) \
BRF_TX_SERIALIZE_(true,return txSerialize.status); \
txSerialize.status = checkDev(loc); \
if (txSerialize.status) \
return txSerialize.status;
#define BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,loc) \
BRF_TX_SERIALIZE; \
txSerialize.status = checkPubFuncEntry(internal,loc); \
if (txSerialize.status) \
return txSerialize.status;
#define BRF_TX_SERIALIZE_NONE BRF_TX_SERIALIZE_(true,return)
#define BRF_RX_SERIALIZE_(waitNow,instr) \
BrfSerialize rxSerialize(this,false,waitNow); \
if (rxSerialize.status) \
instr
#define BRF_RX_SERIALIZE BRF_RX_SERIALIZE_(true,return rxSerialize.status)
#define BRF_RX_SERIALIZE_CHECK_PUB_ENTRY(internal,loc) \
BRF_RX_SERIALIZE; \
rxSerialize.status = checkPubFuncEntry(internal,loc); \
if (rxSerialize.status) \
return rxSerialize.status;
#define BRF_RX_SERIALIZE_NONE BRF_RX_SERIALIZE_(true,return)
BrfLibUsbDevice::BrfLibUsbDevice(BrfInterface* owner)
: m_owner(owner),
m_initialized(false),
m_exiting(false),
m_closing(false),
m_closingDevice(false),
m_notifyOff(false),
m_dbgMutex(false,"BrfDevDbg"),
m_context(0),
m_list(0),
m_listCount(0),
m_devHandle(0),
m_dev(0),
m_devBus(-1),
m_devAddr(-1),
m_devSpeed(LIBUSB_SPEED_HIGH),
m_ctrlTransferPage(0),
m_syncTout(s_lusbSyncTransferTout),
m_syncSemaphore(1,"BrfSync",1),
m_ctrlTout(s_lusbCtrlTransferTout),
m_bulkTout(s_lusbBulkTransferTout),
m_altSetting(BRF_ALTSET_INVALID),
m_rxShowDcInfo(0),
m_rxDcOffsetMax(BRF_RX_DC_OFFSET_DEF),
m_rxDcAvgI(0),
m_rxDcAvgQ(0),
m_freqOffset(BRF_FREQ_OFFS_DEF),
m_txGainCorrSoftware(true),
m_txIO(true),
m_rxIO(false),
m_syncTxStateSet(false),
m_syncTxStateCode(0),
m_rxTimestamp(0),
m_rxResyncCandidate(0),
m_rxTsPastIntervalMs(200),
m_rxTsPastSamples(0),
m_warnClamped(0),
m_minBufsSend(1),
m_silenceTimeMs(0),
m_silenceTs(0),
m_txPowerBalance(1),
m_txPowerBalanceChanged(false),
m_txPowerScaleI(1),
m_txPowerScaleQ(1),
m_wrPowerScaleI(s_sampleEnergize),
m_wrPowerScaleQ(s_sampleEnergize),
m_wrMaxI(s_sampleEnergize),
m_wrMaxQ(s_sampleEnergize),
m_gainExpBreak(0),
m_gainExpSlope(0),
m_phaseExpBreak(0),
m_phaseExpSlope(0),
m_ampTableUse(false),
m_rxAlterDataParams(""),
m_rxAlterData(false),
m_rxAlterIncrement(0),
m_rxAlterTsJumpSingle(true),
m_rxAlterTsJumpPos(0),
m_txPatternChanged(false),
m_txPatternBufPos(0),
m_calLms(false),
m_calibrateStatus(0),
m_calibrateStop(0),
m_calibration(""),
m_calThread(0),
m_sendThread(0),
m_recvThread(0),
m_internalIoSemaphore(1,"BrfDevSyncThreads",1),
m_internalIoTimestamp(0),
m_internalIoTxRate(0),
m_internalIoRxRate(0),
m_internalIoRateChanged(false),
m_threadMutex("BrfDevInternalThread")
{
DDebug(&__plugin,DebugAll,"BrfLibUsbDevice(%p) [%p]",m_owner,this);
m_usbTransfer[EpSendSamples].device = this;
m_usbTransfer[EpSendSamples].ep = BRF_ENDP_TX_SAMPLES;
m_usbTransfer[EpSendCtrl].device = this;
m_usbTransfer[EpSendCtrl].ep = BRF_ENDP_TX_CTRL;
m_usbTransfer[EpReadSamples].device = this;
m_usbTransfer[EpReadSamples].ep = BRF_ENDP_RX_SAMPLES;
m_usbTransfer[EpReadCtrl].device = this;
m_usbTransfer[EpReadCtrl].ep = BRF_ENDP_RX_CTRL;
m_state.m_rx.vga1 = BRF_RXVGA1_GAIN_MAX + 1;
m_state.m_rx.dcOffsetI = BRF_RX_DC_OFFSET_MAX + 1;
m_state.m_rx.dcOffsetQ = BRF_RX_DC_OFFSET_MAX + 1;
m_state.m_tx.vga1 = BRF_TXVGA1_GAIN_MIN - 1;
m_state.m_tx.vga2 = BRF_TXVGA2_GAIN_MIN - 1;
m_state.m_tx.dcOffsetI = BRF_RX_DC_OFFSET_MAX + 1;
m_state.m_tx.dcOffsetQ = BRF_RX_DC_OFFSET_MAX + 1;
initRadioCaps(m_radioCaps);
}
BrfLibUsbDevice::~BrfLibUsbDevice()
{
DDebug(&__plugin,DebugAll,"~BrfLibUsbDevice(%p) [%p]",m_owner,this);
doClose();
}
unsigned int BrfLibUsbDevice::setTxPattern(const String& pattern, float gain)
{
Lock lck(m_dbgMutex);
if (m_state.m_txPattern == pattern && m_state.m_txPatternGain == gain)
return 0;
ComplexVector buf;
unsigned int status = 0;
String e;
unsigned int pLen = 0;
if (pattern &&
!buildVector(e,pattern,buf,totalSamples(true),false,true,false,&pLen,gain)) {
Debug(m_owner,DebugNote,"Invalid tx pattern '%s': %s [%p]",
pattern.c_str(),e.c_str(),m_owner);
status = RadioInterface::Failure;
}
if (!status && buf.length()) {
m_txPattern = buf;
m_state.m_txPattern = pattern;
m_state.m_txPatternGain = gain;
if (m_owner && m_owner->debugAt(DebugNote)) {
String s;
if (!pLen)
pLen = m_txPattern.length();
if (pLen > 30)
pLen = 30;
m_txPattern.head(pLen).dump(s,Math::dumpComplex," ","%g,%g");
if (s.startsWith(m_state.m_txPattern))
s.clear();
else
s.printf(1024,"HEAD[%u]: %s",pLen,s.c_str());
Debug(m_owner,DebugInfo,"TX pattern set to '%s' gain=%.3f len=%u [%p]%s",
m_state.m_txPattern.substr(0,100).c_str(),m_state.m_txPatternGain,
m_txPattern.length(),m_owner,encloseDashes(s,true));
}
}
else {
if (m_state.m_txPattern)
Debug(m_owner,DebugInfo,"TX pattern cleared [%p]",m_owner);
m_txPattern.resetStorage(0);
m_state.m_txPattern.clear();
m_state.m_txPatternGain = 1;
}
m_txPatternChanged = true;
return status;
}
static inline String& dumpIOAvg(String& buf, BrfDevIO& io, uint64_t now)
{
if (io.startTime && io.transferred) {
unsigned int sec = (unsigned int)((now - io.startTime) / 1000000);
if (sec) {
buf = io.transferred / sec;
return (buf << " samples/sec");
}
}
return (buf = "-");
}
void BrfLibUsbDevice::dumpStats(String& buf, const char* sep)
{
BRF_RX_SERIALIZE_NONE;
BRF_TX_SERIALIZE_NONE;
String s;
uint64_t now = Time::now();
buf.append("TxTS=",sep) << m_txIO.timestamp;
buf << sep << "RxTS=" << m_rxIO.timestamp;
buf << sep << "TxAvg=" << dumpIOAvg(s,m_txIO,now);
buf << sep << "RxAvg=" << dumpIOAvg(s,m_rxIO,now);
}
static inline void buildTimestampReport(String& buf, bool tx, uint64_t our, uint64_t board,
unsigned int code, bool app = true)
{
if (!code) {
const char* what = app ? "app" : "crt";
int64_t delta = (int64_t)(our - board);
buf.printf("%s: %s=" FMT64U "\tboard=" FMT64U "\tdelta=" FMT64 "\t%s_position: %s",
brfDir(tx),what,our,board,delta,what,(delta < 0 ? "past" : "future"));
}
else
buf << brfDir(tx) << ": failure - " << RadioInterface::errorName(code);
}
void BrfLibUsbDevice::dumpTimestamps(String& buf, const char* sep)
{
BRF_TX_SERIALIZE_NONE;
uint64_t tsTx = 0;
uint64_t ourTx = m_txIO.lastTs;
unsigned int codeTx = internalGetTimestamp(true,tsTx);
txSerialize.drop();
BRF_RX_SERIALIZE_NONE;
uint64_t tsRx = 0;
uint64_t ourRx = m_rxIO.timestamp;
unsigned int codeRx = internalGetTimestamp(false,tsRx);
uint64_t rx = m_rxTimestamp;
rxSerialize.drop();
String s;
String sTx;
String sRx;
String sRxTs;
buildTimestampReport(sTx,true,ourTx,tsTx,codeTx);
buildTimestampReport(sRx,false,ourRx,tsRx,codeRx);
if (!codeRx)
buildTimestampReport(sRxTs,false,rx,tsRx,codeRx,false);
buf.append(sTx,sep) << sep << sRx;
buf.append(sRxTs,sep);
}
void BrfLibUsbDevice::dumpDev(String& buf, bool info, bool state, const char* sep,
bool fromStatus, bool withHdr)
{
if (!(info || state))
return;
BRF_RX_SERIALIZE_NONE;
BRF_TX_SERIALIZE_NONE;
internalDumpDev(buf,info,state,sep,false,fromStatus,withHdr);
}
void BrfLibUsbDevice::dumpBoardStatus(String& buf, const char* sep)
{
#define ADD_INTERVAL(minVal,maxVal) if (!code) addIntervalInt(buf,minVal,maxVal);
#define BOARD_STATUS_SET_TMP(func,instr_ok) { \
code = func; \
if (!code) \
instr_ok; \
else \
tmp.printf("ERROR %u %s",code,RadioInterface::errorName(code)); \
}
#define BOARD_STATUS_SET(func,instr_ok,prefix,suffix) { \
BOARD_STATUS_SET_TMP(func,instr_ok); \
buf.append(prefix + tmp + (!code ? suffix : ""),sep); \
}
#define dumpDevAppend(func,val,prefix,suffix) BOARD_STATUS_SET(func,tmp = (int64_t)val,prefix,suffix)
#define dumpDevAppendFreq(func,val,prefix,suffix) \
BOARD_STATUS_SET(func,dumpFloatG(tmp,(double)val / 1000000,0,"MHz"),prefix,suffix)
#define reportLpf(tx) { \
BOARD_STATUS_SET(getLpf(intVal,tx),tmp = lookup(intVal,s_lpf),(tx ? "TxLpf=" : "RxLpf="),0); \
if (!code) { \
BOARD_STATUS_SET_TMP(getLpfBandwidth(u32Val,tx),dumpFloatG(tmp,(double)u32Val / 1000000,0,"MHz")); \
buf << " BW: " << tmp; \
} \
}
int intVal = 0;
int16_t int16Val = 0;
uint32_t u32Val = 0;
uint64_t u64Val = 0;
unsigned int code = 0;
String tmp;
dumpDevAppend(getTimestamp(false,u64Val),u64Val,"RxTS=",0);
dumpDevAppend(getTimestamp(true,u64Val),u64Val,"TxTS=",0);
dumpDevAppend(getRxVga1(intVal),intVal,"RxVGA1="," dB");
ADD_INTERVAL(BRF_RXVGA1_GAIN_MIN,BRF_RXVGA1_GAIN_MAX);
dumpDevAppend(getRxVga2(intVal),intVal,"RxVGA2="," dB");
ADD_INTERVAL(BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX);
dumpDevAppend(getTxVga1(intVal),intVal,"TxVGA1="," dB");
ADD_INTERVAL(BRF_TXVGA1_GAIN_MIN,BRF_TXVGA1_GAIN_MAX);
dumpDevAppend(getTxVga2(intVal),intVal,"TxVGA2="," dB");
ADD_INTERVAL(BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX);
dumpDevAppend(getDcOffset(false,true,int16Val),int16Val,"RxDCCorrI=",0);
ADD_INTERVAL(-BRF_RX_DC_OFFSET_MAX,BRF_RX_DC_OFFSET_MAX);
dumpDevAppend(getDcOffset(false,false,int16Val),int16Val,"RxDCCorrQ=",0);
ADD_INTERVAL(-BRF_RX_DC_OFFSET_MAX,BRF_RX_DC_OFFSET_MAX);
dumpDevAppend(getDcOffset(true,true,int16Val),int16Val,"TxDCCorrI=",0);
ADD_INTERVAL(BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX);
dumpDevAppend(getDcOffset(true,false,int16Val),int16Val,"TxDCCorrQ=",0);
ADD_INTERVAL(BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX);
dumpDevAppend(getFpgaCorr(false,CorrFpgaPhase,int16Val),int16Val,"RxCorrFpgaPhase=",0);
ADD_INTERVAL(-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
dumpDevAppend(getFpgaCorr(false,CorrFpgaGain,int16Val),int16Val,"RxCorrFpgaGain=",0);
ADD_INTERVAL(-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
dumpDevAppend(getFpgaCorr(true,CorrFpgaPhase,int16Val),int16Val,"TxCorrFpgaPhase=",0);
ADD_INTERVAL(-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
dumpDevAppend(getFpgaCorr(true,CorrFpgaGain,int16Val),int16Val,"TxCorrFpgaGain=",0);
ADD_INTERVAL(-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
dumpDevAppendFreq(getFrequency(u32Val,false),u32Val,"RxFreq=",0);
dumpDevAppendFreq(getFrequency(u32Val,true),u32Val,"TxFreq=",0);
dumpDevAppend(getSamplerate(u32Val,false),u32Val,"RxSampRate=",0);
dumpDevAppend(getSamplerate(u32Val,true),u32Val,"TxSampRate=",0);
reportLpf(false);
reportLpf(true);
{
BRF_TX_SERIALIZE_NONE;
String tmp;
buf.append("calibration-cache=" + dumpCalCache(tmp),sep);
}
#undef ADD_INTERVAL
#undef BOARD_STATUS_SET_TMP
#undef BOARD_STATUS_SET
#undef dumpDevAppend
#undef dumpDevAppendFreq
#undef reportLpf
}
unsigned int BrfLibUsbDevice::dumpPeripheral(uint8_t dev, uint8_t addr, uint8_t len, String* buf)
{
BRF_TX_SERIALIZE_CHECK_DEV("dumpPeripheral()");
if (dev != UartDevSI5338) {
addr = clampInt(addr,0,0x7f);
len = clampInt(len,1,128 - addr);
}
else {
addr = clampInt(addr,0,256);
len = clampInt(len,1,257 - addr);
}
return internalDumpPeripheral(dev,addr,len,buf,16);
}
// Module reload
void BrfLibUsbDevice::reLoad(const NamedList* params)
{
NamedList dummy("");
if (!params) {
Lock lck(&__plugin);
dummy = *s_cfg.createSection(YSTRING("general"));
params = &dummy;
}
m_warnClamped = (float)params->getIntValue(YSTRING("warn_clamped"),0,0,100);
setDataDump();
checkTs(true,params->getIntValue(YSTRING("txcheckts"),0));
checkTs(false,params->getIntValue(YSTRING("rxcheckts"),-1));
checkLimit(false,params->getIntValue(YSTRING("rxchecklimit"),0));
updateAlterData(*params);
const NamedString* p = params->getParam(YSTRING("rxoutsw"));
if (p)
setRxOut(p->toBoolean());
m_trace = params->getBoolValue(YSTRING("trace_discipliner"));
if (!m_dumpDelays)
m_dumpDelays = params->getIntValue(YSTRING("trace_discipliner_delays"),0,0);
}
// dir: 0=both negative=rx positive=tx
// level: 0: both negative=app positive=device
void BrfLibUsbDevice::setDataDump(int dir, int level, const NamedList* p)
{
static const String prefix[] = {"tx-data","tx-app","rx-data","rx-app"};
NamedList dummy("");
if (!p) {
Lock lck(__plugin);
dummy = *s_cfg.createSection(YSTRING("filedump"));
p = &dummy;
}
NamedList* upd[4] = {0,0,0,0};
if (dir >= 0) {
if (level >= 0)
upd[0] = &m_txIO.dataDumpParams;
if (level <= 0)
upd[1] = &m_txIO.upDumpParams;
}
if (dir <= 0) {
if (level >= 0)
upd[2] = &m_rxIO.dataDumpParams;
if (level <= 0)
upd[3] = &m_rxIO.upDumpParams;
}
Lock lck(m_dbgMutex);
for (unsigned int i = 0; i < 4; i++) {
if (!upd[i])
continue;
const String& mode = (*p)[prefix[i] + "-mode"];
int n = 0;
if (mode == YSTRING("count")) {
String param = prefix[i] + "-count";
const String& s = (*p)[param];
if (s) {
n = s.toInteger(-1);
if (n <= 0) {
Debug(m_owner,DebugConf,"%s set to '%s': disabling dump [%p]",
param.c_str(),s.c_str(),m_owner);
n = 0;
}
}
else
n = 10;
}
else if (mode.toBoolean())
n = -1;
String file;
if (n) {
file = (*p)[prefix[i] + "-file"];
if (!file)
file = prefix[i] + "-${boardserial}";
}
upd[i]->clearParams();
if (file) {
upd[i]->addParam("file",file);
upd[i]->addParam("count",String(n));
}
// Signal change
upd[i]->assign("1");
}
}
// Initialize the device.
// Call the reset method in order to set the device to a known state
unsigned int BrfLibUsbDevice::open(const NamedList& params, String& error)
{
BRF_RX_SERIALIZE;
BRF_TX_SERIALIZE;
doClose();
String e;
unsigned int status = 0;
while (true) {
m_calLms = params.getBoolValue(YSTRING("lms_autocal"));
m_serial = params[YSTRING("serial")];
BRF_FUNC_CALL_BREAK(resetUsb(&e));
BRF_FUNC_CALL_BREAK(openDevice(true,&e));
BRF_FUNC_CALL_BREAK(updateSpeed(params,&e));
m_calCache.clear();
readCalCache();
status = updateFpga(params);
if (status) {
e = "Failed to load FPGA";
break;
}
BRF_FUNC_CALL_BREAK(lusbSetAltInterface(BRF_ALTSET_IDLE,&e));
BRF_FUNC_CALL_BREAK(openChangeLms(params,&e));
BrfDevTmpAltSet tmpAltSet(this,status,&e,"Open device");
if (status)
break;
uint8_t data = 0;
BRF_FUNC_CALL_BREAK(lmsRead(0x04,data,&e));
m_lmsVersion.printf("0x%x (%u.%u)",data,(data >> 4),(data & 0x0f));
BRF_FUNC_CALL_BREAK(tmpAltSet.restore());
m_freqOffset = clampFloatParam(params,YSTRING("RadioFrequencyOffset"),
BRF_FREQ_OFFS_DEF,BRF_FREQ_OFFS_MIN,BRF_FREQ_OFFS_MAX);
m_txGainCorrSoftware = params.getBoolValue(YSTRING("tx_fpga_corr_gain_software"),true);
bool superSpeed = (speed() == LIBUSB_SPEED_SUPER);
m_maxDelay = clampIntParam(params,YSTRING("max_delay"),superSpeed ?
BRF_MAX_DELAY_SUPER_SPEED_DEF : BRF_MAX_DELAY_HIGH_SPEED_DEF,100,2000);
m_bestDelay = clampIntParam(params,YSTRING("best_delay"),superSpeed ?
BRF_BEST_DELAY_SUPER_SPEED_DEF : BRF_BEST_DELAY_HIGH_SPEED_DEF,100,m_maxDelay);
m_knownDelay = clampIntParam(params,YSTRING("known_delay"),superSpeed ?
BRF_KNOWN_DELAY_SUPER_SPEED_DEF : BRF_KNOWN_DELAY_HIGH_SPEED_DEF,100,m_bestDelay);
m_systemAccuracy = clampIntParam(params,YSTRING("system_accuracy"),
BRF_SYSTEM_ACCURACY_DEF,100,2000);
m_accuracyPpb = clampIntParam(params,YSTRING("accuracy_ppb"),BRF_ACCURACY_PPB_DEF,10,200);
// Init TX/RX buffers
m_rxResyncCandidate = 0;
m_state.m_rxDcAuto = params.getBoolValue("rx_dc_autocorrect",true);
m_rxShowDcInfo = params.getIntValue("rx_dc_showinfo");
m_rxDcOffsetMax = BRF_RX_DC_OFFSET_DEF;
m_state.m_rx.dcOffsetI = BRF_RX_DC_OFFSET_MAX + 1;
m_state.m_rx.dcOffsetQ = BRF_RX_DC_OFFSET_MAX + 1;
int tmpInt = 0;
int i = 0;
int q = 0;
i = clampIntParam(params,"RX.OffsetI",0,-BRF_RX_DC_OFFSET_MAX,BRF_RX_DC_OFFSET_MAX);
q = clampIntParam(params,"RX.OffsetQ",0,-BRF_RX_DC_OFFSET_MAX,BRF_RX_DC_OFFSET_MAX);
BRF_FUNC_CALL_BREAK(internalSetCorrectionIQ(false,i,q,&e));
BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,true,&e));
BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,false,&e));
i = clampIntParam(params,"TX.OffsetI",0,BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX);
q = clampIntParam(params,"TX.OffsetQ",0,BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX);
BRF_FUNC_CALL_BREAK(internalSetCorrectionIQ(true,i,q,&e));
// Set RX gain
m_state.m_rx.vga1 = BRF_RXVGA1_GAIN_MAX + 1;
BRF_FUNC_CALL_BREAK(internalSetGain(false,BRF_RXVGA2_GAIN_MIN));
// Pre/post mixer TX VGA
m_state.m_tx.vga1Changed = false;
const String& txVga1 = params["tx_vga1"];
if (txVga1)
BRF_FUNC_CALL_BREAK(internalSetTxVga(txVga1.toInteger(BRF_TXVGA1_GAIN_DEF),true,&e));
const String& txVga2 = params["tx_vga2"];
if (txVga2)
BRF_FUNC_CALL_BREAK(internalSetTxVga(txVga2.toInteger(BRF_TXVGA2_GAIN_MIN),false,&e));
// Set FPGA correction
tmpInt = clampIntParam(params,"tx_fpga_corr_phase",0,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
status = internalSetFpgaCorr(true,CorrFpgaPhase,tmpInt,&e,DebugConf);
if (status)
break;
tmpInt = clampIntParam(params,"tx_fpga_corr_gain",0,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX);
status = internalSetFpgaCorr(true,CorrFpgaGain,tmpInt,&e,DebugConf);
if (status)
break;
// Make sure we have the correct values for status
BRF_FUNC_CALL_BREAK(updateStatus(&e));
// Set tx I/Q balance
if (!m_txGainCorrSoftware) {
const String& txPB = params["tx_powerbalance"];
internalSetTxIQBalance(false,txPB.toDouble(1),"tx_powerbalance");
}
// Set some optional params
setTxPattern(params["txpattern"]);
showBuf(true,params.getIntValue("txbufoutput",0),
params.getBoolValue("txbufoutput_nodata"));
showBuf(false,params.getIntValue("rxbufoutput",0),
params.getBoolValue("rxbufoutput_nodata"));
m_silenceTimeMs = clampIntParam(params,"silence_time",5000,0,60000);
m_rxTsPastIntervalMs = clampIntParam(params,"rx_ts_past_error_interval",200,50,10000);
const String& sRateSamples = params[YSTRING("srate_buffered_samples")];
if (sRateSamples) {
const char* s = BrfBufsThreshold::init(m_bufThres,sRateSamples,m_radioCaps);
if (s)
Debug(m_owner,DebugConf,"Failed to parse srate_buffered_samples='%s': %s [%p]",
sRateSamples.c_str(),s,m_owner);
}
break;
}
if (status) {
Debug(m_owner,DebugWarn,"Failed to open USB device: %s [%p]",
e.safe("Unknown error"),m_owner);
doClose();
error = e;
return status;
}
String s;
internalDumpDev(s,true,false,"\r\n",true);
Debug(m_owner,DebugAll,"Opened device [%p]%s",m_owner,encloseDashes(s,true));
txSerialize.drop();
rxSerialize.drop();
reLoad(&params);
return status;
}
// Initialize operating parameters
unsigned int BrfLibUsbDevice::initialize(const NamedList& params)
{
BRF_RX_SERIALIZE;
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"initialize()");
if (m_initialized)
return 0;
String s;
//params.dump(s,"\r\n");
Debug(m_owner,DebugAll,"Initializing ... [%p]%s",m_owner,encloseDashes(s,true));
String e;
unsigned int status = 0;
while (true) {
// Check for radio operating params
// FILTER, SAMPLERATE, BAND, TX/RX FREQ
const String& bw = params["filter"];
if (bw) {
unsigned int tmp = bw.toInteger(1,0,1);
BRF_FUNC_CALL_BREAK(internalSetLpfBandwidthBoth(tmp,&e));
}
const String& sr = params["samplerate"];
if (sr) {
unsigned int tmp = sr.toInteger(1,0,1);
BRF_FUNC_CALL_BREAK(internalSetSampleRateBoth(tmp,&e));
}
for (int i = 0; i < 2; i++) {
bool tx = (i == 0);
const NamedString* ns = params.getParam(tx ? "txfrequency" : "rxfrequency");
if (!ns)
continue;
BRF_FUNC_CALL_BREAK(internalSetFrequency(tx,ns->toInt64(),&e));
}
if (status)
break;
BRF_FUNC_CALL_BREAK(internalPowerOn(true,true,true,&e));
break;
}
if (!status) {
txSerialize.drop();
rxSerialize.drop();
m_initialized = true;
if (params.getBoolValue(YSTRING("calibrate"))) {
NamedList tmp("");
tmp.copySubParams(params,"calibrate_");
status = calibrate(tmp.getBoolValue(YSTRING("sync")),tmp,&e,true);
}
else {
m_notifyOff = true;
Message* m = buildNotify("start");
BrfDevDirState& dir = getDirState(true);
m->addParam("tx_frequency",String(dir.frequency));
m->addParam("tx_samplerate",String(dir.sampleRate));
m->addParam("tx_filter",String(dir.lpfBw));
Engine::dispatch(*m);
// Lock TX. Re-check our state. Apply params
txSerialize.wait();
if (!txSerialize.status) {
status = checkPubFuncEntry(false,"initialize()");
if (!status)
status = applyStartParams(*m,&e);
}
else
status = txSerialize.status;
TelEngine::destruct(m);
}
if ((!status || status == RadioInterface::Pending) &&
m_owner && m_owner->debugAt(DebugAll)) {
String s;
#ifdef DEBUG
if (!status)
internalDumpDev(s,false,true,"\r\n",true,true,true);
#endif
Debug(m_owner,DebugAll,"Initialized [%p]%s",m_owner,encloseDashes(s,true));
}
if (!status)
return 0;
}
if (status != RadioInterface::Pending)
Debug(m_owner,DebugCrit,"Failed to initialize: %s [%p]",
e.safe("Unknown error"),m_owner);
return status;
}
// Check if parameters are set
unsigned int BrfLibUsbDevice::isInitialized(bool checkTx, bool checkRx, String* error)
{
if (!m_initialized)
return setErrorNotInit(error);
for (int i = 0; i < 2; i++) {
bool tx = (i == 0);
if ((tx && !checkTx) || (!tx && !checkRx))
continue;
BrfDevDirState& s = getDirState(tx);
if (!s.frequency)
return setErrorNotInit(error,String(brfDir(tx)) + " frequency not set");
if (!s.sampleRate)
return setErrorNotInit(error,String(brfDir(tx)) + " sample rate not set");
if (!s.lpfBw)
return setErrorNotInit(error,String(brfDir(tx)) + " filter bandwidth not set");
}
return 0;
}
// Close the device
void BrfLibUsbDevice::close()
{
BRF_RX_SERIALIZE_NONE;
BRF_TX_SERIALIZE_NONE;
doClose();
}
// Power on the radio
// Enable timestamps, enable RF TX/RX
unsigned int BrfLibUsbDevice::powerOn()
{
BRF_RX_SERIALIZE;
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"powerOn()");
return internalPowerOn(true);
}
// Send an array of samples waiting to be transmitted
unsigned int BrfLibUsbDevice::syncTx(uint64_t ts, float* data, unsigned int samples,
float* powerScale, bool internal)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"syncTx()");
unsigned int status = send(ts,data,samples,powerScale);
if (status == RadioInterface::HardwareIOError) {
txSerialize.drop();
Thread::yield();
}
return status;
}
// Receive data from the Rx interface of the bladeRF device
unsigned int BrfLibUsbDevice::syncRx(uint64_t& ts, float* data, unsigned int& samples,
String* error, bool internal)
{
BRF_RX_SERIALIZE_CHECK_PUB_ENTRY(internal,"syncRx()");
unsigned int status = recv(ts,data,samples,error);
if (status == RadioInterface::HardwareIOError) {
rxSerialize.drop();
Thread::yield();
}
return status;
}
// Capture RX data
unsigned int BrfLibUsbDevice::capture(bool tx, float* buf, unsigned int samples,
uint64_t& ts, String* error)
{
if (!(buf && samples))
return 0;
BrfDevIO& io = getIO(tx);
Lock lck(io.captureMutex);
if (io.captureBuf)
return setErrorFail(error,"Duplicate capture");
io.captureSamples = samples;
io.captureTs = ts;
io.captureOffset = 0;
io.captureStatus = 0;
io.captureError.clear();
io.captureBuf = buf;
lck.drop();
unsigned int tout = ((samples + 999) / 1000) * 20;
unsigned int status = 0;
unsigned int intervals = threadIdleIntervals(tout);
while (!status && io.captureBuf) {
io.captureSemaphore.lock(Thread::idleUsec());
status = cancelled(error);
if (!status && ((intervals--) == 0))
status = setErrorTimeout(error,"Capture timeout");
}
lck.acquire(io.captureMutex);
if (!io.captureBuf) {
ts = io.captureTs;
if (io.captureStatus && error)
*error = io.captureError;
return io.captureStatus;
}
io.captureBuf = 0;
return status;
}
unsigned int BrfLibUsbDevice::setFrequency(uint64_t hz, bool tx)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setFrequency()");
return internalSetFrequency(tx,hz);
}
unsigned int BrfLibUsbDevice::getFrequency(uint32_t& hz, bool tx)
{
BRF_TX_SERIALIZE_CHECK_DEV("getFrequency()");
return internalGetFrequency(tx,&hz);
}
// Set frequency offset
unsigned int BrfLibUsbDevice::setFreqOffset(float offs, float* newVal, bool stopAutoCal)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setFreqOffset()");
unsigned int status = internalSetFreqOffs(offs,newVal);
txSerialize.drop();
if (!status && stopAutoCal && getDirState(true).rfEnabled)
disableDiscipline(true);
return status;
}
// Get frequency offset
unsigned int BrfLibUsbDevice::getFreqOffset(float& offs)
{
BRF_TX_SERIALIZE_CHECK_DEV("getFreqOffset()");
String val;
unsigned int status = getCalField(val,"DAC","DAC_TRIM");
if (status == 0) {
offs = val.toInteger() / 256.0;
// TODO m_freqOffset = offs ???
}
return status;
}
// Set the bandwidth for a specific module
unsigned int BrfLibUsbDevice::setLpfBandwidth(uint32_t band, bool tx)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setLpfBandwidth()");
return internalSetLpfBandwidth(tx,band);
}
// Get the bandwidth for a specific module
unsigned int BrfLibUsbDevice::getLpfBandwidth(uint32_t& band, bool tx)
{
BRF_TX_SERIALIZE_CHECK_DEV("getLpfBandwidth()");
String e;
unsigned int status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
if (status == 0) {
uint8_t data = 0;
status = lmsRead(lmsLpfAddr(tx),data,&e);
if (status == 0) {
data >>= 2;
data &= 0xf;
band = index2bw(15 - data);
getDirState(tx).lpfBw = band;
}
}
if (status == 0)
XDebug(m_owner,DebugAll,"Got %s LPF bandwidth %u [%p]",brfDir(tx),band,m_owner);
else
Debug(m_owner,DebugNote,"Failed to retrieve %s LPF bandwidth: %s [%p]",
brfDir(tx),e.c_str(),m_owner);
return status;
}
unsigned int BrfLibUsbDevice::setLpf(int lpf, bool tx)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setLpf()");
return internalSetLpf(tx,lpf);
}
unsigned int BrfLibUsbDevice::getLpf(int& lpf, bool tx)
{
BRF_TX_SERIALIZE_CHECK_DEV("getLpf()");
return internalGetLpf(tx,&lpf);
}
// Set the sample rate on a specific module
unsigned int BrfLibUsbDevice::setSamplerate(uint32_t value, bool tx)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setSamplerate()");
return internalSetSampleRate(tx,value);
}
// Get the sample rate on a specific module
unsigned int BrfLibUsbDevice::getSamplerate(uint32_t& value, bool tx)
{
BRF_TX_SERIALIZE_CHECK_DEV("getSamplerate()");
String e;
unsigned int status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
while (!status) {
BrfRationalRate rate;
Si5338MultiSynth synth;
uint8_t val = 0;
synth.index = 1;
if (tx)
synth.index = 2;
synth.base = 53 + synth.index * 11;
// Read the enable bits
if ((status = getSi5338(36 + synth.index,val,&e)) != 0)
break;
synth.enable = val&7;
// Read all of the multisynth registers
for (int i = 0; i < 10; i++)
if ((status = getSi5338(synth.base + i,synth.regs[i],&e)) != 0)
break;
if (status)
break;
// Populate the RxDIV value from the register
if ((status = getSi5338(31 + synth.index,val,&e)) != 0)
break;
// RxDIV is stored as a power of 2, so restore it on readback
val = (val>>2)&7;
synth.r = (1<<val);
// Unpack the regs into appropriate values
unpackRegs(synth);
calcSrate(synth,rate);
if (rate.integer > 0xffffffff) {
e = "The value for the sample rate is too big";
status = RadioInterface::Failure;
break;
}
if (rate.numerator)
Debug(m_owner,DebugMild,
"Truncating the %s fractional part of the samplerate [%p]",
brfDir(tx),m_owner);
value = (uint32_t)rate.integer;
getDirState(tx).sampleRate = value;
break;
}
if (status == 0)
XDebug(m_owner,DebugAll,"Got %s samplerate %u [%p]",brfDir(tx),value,m_owner);
else
Debug(m_owner,DebugNote,"Failed to get %s samplerate: %s [%p]",
brfDir(tx),e.c_str(),m_owner);
return status;
}
// Set the pre-mixer gain on transmission (interval [-35..-4])
// Set the post-mixer gain setting on transmission (interval: [0..25])
unsigned int BrfLibUsbDevice::setTxVga(int vga, bool preMixer)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setTxVga()");
return internalSetTxVga(vga,preMixer);
}
// Retrieve the pre/post mixer gain setting on transmission
unsigned int BrfLibUsbDevice::getTxVga(int& vga, bool preMixer)
{
BRF_TX_SERIALIZE_CHECK_DEV("getTxVga()");
return internalGetTxVga(&vga,preMixer);
}
// Set TX gain expansion
unsigned int BrfLibUsbDevice::setGainExp(float breakpoint, float max)
{
m_gainExpBreak = pow(10,breakpoint*0.1);
// the base slope is 1, so max gain of 1 is a slope of zero
// we correct for the by subtracting 1 from the max
// we will add it back after we compute the expansion factor
m_gainExpSlope = (max-1) / (2-breakpoint);
calculateAmpTable();
return 0;
}
// Set TX phase expansion
unsigned int BrfLibUsbDevice::setPhaseExp(float breakpoint, float max)
{
m_phaseExpBreak = pow(10,breakpoint*0.1);
// convert max to radians
max = max * M_PI / 180.0;
m_phaseExpSlope = max / (2-breakpoint);
calculateAmpTable();
return 0;
}
unsigned int BrfLibUsbDevice::setTxIQBalance(float value)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setTxIQBalance()");
return internalSetTxIQBalance(false,value);
}
// Enable or disable the pre/post mixer gain on the receive side
unsigned int BrfLibUsbDevice::enableRxVga(bool on, bool preMixer)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"enableRxVga()");
return internalEnableRxVga(on,preMixer);
}
// Set the pre-mixer gain setting on the receive side (interval [5..30])
// Set the post-mixer Rx gain setting (interval [0..30])
unsigned int BrfLibUsbDevice::setRxVga(int vga, bool preMixer)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setRxVga()");
return internalSetRxVga(vga,preMixer);
}
// Retrieve the pre/post mixer rx gain setting
unsigned int BrfLibUsbDevice::getRxVga(int& vga, bool preMixer)
{
BRF_TX_SERIALIZE_CHECK_DEV("getRxVga()");
return internalGetRxVga(&vga,preMixer);
}
// Set pre and post mixer value
unsigned int BrfLibUsbDevice::setGain(bool tx, int val, int* newVal)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setGain()");
return internalSetGain(tx,val,newVal);
}
// Run check / calibration procedure
unsigned int BrfLibUsbDevice::calibrate(bool sync, const NamedList& params,
String* error, bool fromInit)
{
BRF_RX_SERIALIZE;
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"calibrate()");
#ifdef DEBUG
Debugger d(DebugAll,"CALIBRATE"," %s sync=%s [%p]",
m_owner->debugName(),String::boolText(sync),m_owner);
#endif
int rxDcAutoRestore = -1;
String e;
unsigned int status = 0;
if (!m_initialized)
status = setError(RadioInterface::NotInitialized,&e,"not initialized");
BrfDuration duration;
// force the VCTCXO discipliner to drop it's data
if (sync)
postponeActivity(1,true);
while (!status) {
if (!sync) {
if (m_owner && fromInit)
m_owner->setPending(RadioInterface::PendingInitialize);
BRF_FUNC_CALL_BREAK(startCalibrateThreads(&e,params));
status = RadioInterface::Pending;
break;
}
BrfDevTmpAltSet tmpAltSet(this,status,&e,"Calibrate");
if (status)
break;
rxDcAutoRestore = setRxDcAuto(false) ? 1 : 0;
m_calibrateStatus = Calibrating;
Debug(m_owner,DebugInfo,"Calibrating ... [%p]",m_owner);
// Drop lock. We are going to use public functions to calibrate
txSerialize.drop();
rxSerialize.drop();
// LMS autocalibration
if (params.getBoolValue("device_autocal",true))
BRF_FUNC_CALL_BREAK(calibrateAuto(&e));
// Check
if (params.getBoolValue("loopback_check",true))
BRF_FUNC_CALL_BREAK(loopbackCheck(&e));
// Baseband calibration
BRF_FUNC_CALL_BREAK(calibrateBaseband(&e));
break;
}
duration.stop();
if (rxDcAutoRestore > 0)
setRxDcAuto(true);
// Avoid hard cancelling if we are in calibration thread
if (m_calThread && m_calThread == Thread::current())
m_calThread = 0;
if (sync) {
stopThreads();
if (m_owner && fromInit)
m_owner->setPending(RadioInterface::PendingInitialize,status);
// Calibration done
m_calibrateStatus = status ? Calibrate : Calibrated;
// Notify
Message* m = buildNotify("calibrated");
if (!status)
m->copyParams(m_calibration);
else
m_owner->setError(*m,status,e);
Engine::enqueue(m);
if (!status) {
Debug(m_owner,DebugInfo,"Calibration finished in %s [%p]",
duration.secStr(),m_owner);
return 0;
}
}
else if (RadioInterface::Pending == status) {
Debug(m_owner,DebugAll,"Async calibration started [%p]",m_owner);
return status;
}
return showError(status,e.c_str(),"Calibration failed",error,DebugWarn);
}
// Set Tx/Rx DC I/Q offset correction
unsigned int BrfLibUsbDevice::setDcOffset(bool tx, bool i, int16_t value)
{
int rxDcAutoRestore = -1;
if (!tx) {
// Temporary disable RX auto correct
BRF_RX_SERIALIZE_CHECK_PUB_ENTRY(false,"setDcOffset()");
rxDcAutoRestore = setRxDcAuto(false) ? 1 : 0;
}
BrfSerialize txSerialize(this,true,true);
if (!txSerialize.status)
txSerialize.status = checkPubFuncEntry(false,"setDcOffset()");
if (txSerialize.status) {
if (rxDcAutoRestore > 0)
m_state.m_rxDcAuto = true;
return txSerialize.status;
}
unsigned int status = internalSetDcOffset(tx,i,value);
if (tx)
return status;
if (status == 0) {
// Don't restore old RX DC autocorrect: the values are set by the upper layer
if (rxDcAutoRestore > 0)
Debug(m_owner,DebugInfo,
"Disabled RX DC autocorrect: I/Q values set by the upper layer [%p]",this);
}
else if (rxDcAutoRestore > 0)
// Failure: restore old RX DC autocorrect
m_state.m_rxDcAuto = true;
return status;
}
// Retrieve Tx/Rx DC I/Q offset correction
unsigned int BrfLibUsbDevice::getDcOffset(bool tx, bool i, int16_t& value)
{
BRF_TX_SERIALIZE_CHECK_DEV("getDcOffset()");
return internalGetDcOffset(tx,i,&value);
}
unsigned int BrfLibUsbDevice::setFpgaCorr(bool tx, int corr, int16_t value)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setFpgaCorr()");
return internalSetFpgaCorr(tx,corr,value);
}
unsigned int BrfLibUsbDevice::getFpgaCorr(bool tx, int corr, int16_t& value)
{
BRF_TX_SERIALIZE_CHECK_DEV("getFpgaCorr()");
int16_t v = 0;
unsigned int status = internalGetFpgaCorr(tx,corr,&v);
value = v;
return status;
}
unsigned int BrfLibUsbDevice::getTimestamp(bool tx, uint64_t& ts)
{
BRF_TX_SERIALIZE_CHECK_DEV("getTimestamp()");
return internalGetTimestamp(tx,ts);
}
unsigned int BrfLibUsbDevice::samplesAndTimestamp(uint64_t& samples, uint64_t& timestamp,
uint16_t& delay, String* serializeErr)
{
BrfSerialize txSerialize(this,true,false);
txSerialize.wait(serializeErr,12000);
if (!txSerialize.status)
txSerialize.status = checkDev("samplesAndTimestamp()");
if (!txSerialize.status) {
uint64_t initial = Time::now();
txSerialize.status = internalGetTimestamp(true,samples);
timestamp = Time::now();
if (!txSerialize.status && timestamp > initial) {
delay = timestamp - initial;
timestamp = (timestamp + initial) / 2;
return 0;
}
}
samples = 0;
return txSerialize.status;
}
unsigned int BrfLibUsbDevice::writeLMS(uint8_t addr, uint8_t value, uint8_t* rst,
String* error, bool internal)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"writeLMS()");
if (rst)
return lmsSet(addr,value,*rst,error);
return lmsWrite(addr,value,error);
}
unsigned int BrfLibUsbDevice::writeLMS(const String& str, String* error, bool internal)
{
if (!str)
return 0;
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"writeLMS()");
return lmsWrite(str,!internal,error);
}
// Read LMS register(s)
unsigned int BrfLibUsbDevice::readLMS(uint8_t addr, uint8_t& value, String* error,
bool internal)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"readLMS()");
return lmsRead(addr & 0x7f,value,error);
}
unsigned int BrfLibUsbDevice::readLMS(String& dest, const String* read,
bool readIsInterleaved, String* error, bool internal)
{
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"readLMS()");
return lmsRead(dest,read,readIsInterleaved,error);
}
// Check LMS registers
unsigned int BrfLibUsbDevice::checkLMS(const String& what, String* error, bool internal)
{
if (!what)
return 0;
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(internal,"checkLMS()");
return lmsCheck(what,error);
}
unsigned int BrfLibUsbDevice::setLoopback(const char* name, const NamedList& params)
{
int mode = LoopNone;
if (!TelEngine::null(name))
mode = lookup(name,s_loopback,LoopUnknown);
if (mode == LoopUnknown) {
Debug(m_owner,DebugNote,"Unknown loopback mode '%s' [%p]",name,m_owner);
return RadioInterface::OutOfRange;
}
BRF_TX_SERIALIZE_CHECK_PUB_ENTRY(false,"setLoopback()");
return internalSetLoopback(mode,params);
}
// Set parameter(s)
unsigned int BrfLibUsbDevice::setParam(const String& param, const String& value,
const NamedList& params)
{
if (!param)
return 0;
if (param == YSTRING("calibrate_bb_dc_dump")) {
Lock lck(m_dbgMutex);
m_bbCalDcFile = value;
}
else if (param == YSTRING("calibrate_bb_imbalance_dump")) {
Lock lck(m_dbgMutex);
m_bbCalImbalanceFile = value;
}
else if (param == YSTRING("device_check_dump")) {
Lock lck(m_dbgMutex);
m_devCheckFile = value;
}
else {
Debug(m_owner,DebugNote,"Unknown device param '%s' [%p]",param.c_str(),m_owner);
return RadioInterface::NotSupported;
}
Debug(m_owner,DebugAll,"Handled param set '%s'='%s' [%p]",
param.c_str(),value.c_str(),m_owner);
return 0;
}
static inline unsigned int ts2buffers(uint64_t ts, unsigned int len)
{
return (unsigned int)((ts + len - 1) / len);
}
// Utility: run device send data
void BrfLibUsbDevice::runSend(BrfThread* th)
{
if (!th)
return;
unsigned int samples = 0;
unsigned int rxLatency = 0;
unsigned int txBuffers = 0;
ComplexVector buf;
bool wait = true;
uint64_t rxTs = 0;
uint64_t ts = 0;
unsigned int status = getTimestamp(true,ts);
uint64_t silence = ts + 200000;
bool paused = true;
while (!status && (0 == cancelled())) {
if (th->paused(true,ts,status) || status) {
if (!status)
Thread::idle();
silence = ts + 200000;
wait = true;
setIoDontWarnTs(true);
paused = true;
continue;
}
else if (paused) {
paused = false;
samples = totalSamples(true);
if (!samples)
break;
if (samples != buf.length()) {
rxLatency = (m_radioCaps.rxLatency + samples - 1) / samples;
txBuffers = (m_radioCaps.txLatency + samples - 1) / samples;
buf.resetStorage(samples);
}
}
// Wait for RX
if (wait) {
while (!status && !m_internalIoSemaphore.lock(Thread::idleUsec()))
status = cancelled();
if (status)
break;
if (th->isPaused()) {
m_internalIoSemaphore.unlock();
continue;
}
Lock lck(m_threadMutex);
rxTs = m_internalIoTimestamp;
}
else
wait = true;
uint64_t crtRxTs = rxTs + rxLatency;
unsigned int sendCount = txBuffers;
if (ts >= crtRxTs) {
// TX time is at least RX time. Start sending from it
unsigned int diff = ts2buffers(ts - crtRxTs,samples);
if (sendCount > diff)
sendCount -= diff;
else
sendCount = 0;
}
else {
// Underrun. Start sending from RX time
if (crtRxTs > silence) {
unsigned int u = ts2buffers(crtRxTs - ts,samples);
if (u > 1)
Debug(m_owner,u > 5 ? DebugNote : DebugAll,
"Internal transmit underrun by %u buffer(s) [%p]",u,m_owner);
else
DDebug(m_owner,DebugAll,
"Internal transmit underrun by %u buffer(s) [%p]",u,m_owner);
}
ts = crtRxTs;
}
while (!status && sendCount--) {
status = syncTx(ts,(float*)buf.data(),buf.length(),0,true);
ts += buf.length();
}
if (status)
break;
// Look again at RX time
Lock lck(m_threadMutex);
if (m_internalIoTimestamp < crtRxTs) {
wait = false;
rxTs = crtRxTs;
}
}
}
// Utility: run device send data
void BrfLibUsbDevice::runRecv(BrfThread* th)
{
if (!th)
return;
ComplexVector buf(totalSamples(false));
uint64_t ts = 0;
unsigned int status = getTimestamp(false,ts);
unsigned int txRate = 0;
unsigned int rxRate = 0;
m_internalIoRateChanged = true;
bool paused = true;
while (!status && (0 == cancelled())) {
if (th->paused(false,ts,status) || status) {
if (!status) {
m_internalIoSemaphore.unlock();
Thread::idle();
setIoDontWarnTs(false);
}
paused = true;
continue;
}
else if (paused) {
paused = false;
if (totalSamples(false) != buf.length())
buf.resetStorage(totalSamples(false));
}
// Simulate some processing to avoid keeping the RX mutex locked
generateExpTone(buf,0);
buf.bzero();
unsigned int len = buf.length();
BRF_FUNC_CALL_BREAK(syncRx(ts,(float*)buf.data(),len,0,true));
ts += len;
m_threadMutex.lock();
if (m_internalIoRateChanged) {
txRate = m_internalIoTxRate;
rxRate = m_internalIoRxRate;
m_internalIoRateChanged = false;
}
if (txRate != rxRate && txRate && rxRate)
m_internalIoTimestamp = (ts * txRate) / rxRate;
else
m_internalIoTimestamp = ts;
m_threadMutex.unlock();
m_internalIoSemaphore.unlock();
}
m_internalIoSemaphore.unlock();
}
// Build notification message
Message* BrfLibUsbDevice::buildNotify(const char* status)
{
Message* m = new Message("module.update",0,true);
m->addParam("module",__plugin.name());
m_owner->completeDevInfo(*m,true);
m->addParam("status",status,false);
return m;
}
// Release data
void BrfLibUsbDevice::destruct()
{
doClose();
for (unsigned int i = 0; i < EpCount; i++)
m_usbTransfer[i].reset();
GenObject::destruct();
}
uint64_t BrfLibUsbDevice::reduceFurther(uint64_t v1, uint64_t v2)
{
if (!(v1 && v2))
return 1;
while (v2) {
uint64_t tmp = v1 % v2;
v1 = v2;
v2 = tmp;
}
return v1;
}
void BrfLibUsbDevice::reduceRational(BrfRationalRate& rate)
{
while (rate.denominator > 0 && rate.numerator >= rate.denominator) {
rate.numerator = rate.numerator - rate.denominator;
rate.integer++;
}
// Reduce what's left of the fraction
uint64_t val = reduceFurther(rate.numerator,rate.denominator);
if (val) {
rate.numerator /= val;
rate.denominator /= val;
}
}
void BrfLibUsbDevice::calcSrate(Si5338MultiSynth& synth, BrfRationalRate& rate)
{
BrfRationalRate tmp;
tmp.integer = synth.a;
tmp.numerator = synth.b;
tmp.denominator = synth.c;
rate.integer = 0;
rate.numerator = SI5338_F_VCO * tmp.denominator;
rate.denominator = (uint64_t)synth.r * 2 *
(tmp.integer * tmp.denominator + tmp.numerator);
reduceRational(rate);
}
unsigned int BrfLibUsbDevice::calcMultiSynth(Si5338MultiSynth& synth,
BrfRationalRate& rate, String* error)
{
BrfRationalRate tmp;
// Double requested frequency since LMS requires 2:1 clock:sample rate
rationalDouble(rate);
// Find a suitable R value
uint8_t rValue = 1;
uint8_t rPower = 0;
while (rate.integer < 5000000 && rValue < 32) {
rationalDouble(rate);
rValue <<= 1;
rPower++;
}
if (rValue == 32 && rate.integer < 5000000)
return setError(RadioInterface::Failure,error,"Multi synth calculation failed");
// Find suitable MS (a, b, c) values
tmp.integer = 0;
tmp.numerator= SI5338_F_VCO * rate.denominator;
tmp.denominator= rate.integer * rate.denominator + rate.numerator;
reduceRational(tmp);
// Check values to make sure they are OK
if (tmp.integer < 8 || tmp.integer > 567)
return setError(RadioInterface::Failure,error,
"Multi synth calculation - the integer part is out of bounds");
// Loss of precision if numeratoror denominatorare greater than 2^30-1
bool warn = true;
while (tmp.numerator > (1 << 30) || tmp.denominator > (1 << 30)) {
if (warn) {
warn = false;
Debug(&__plugin,DebugMild,
"Multi synth calculation: numerator or denominator are too big, we'll loose precision");
}
tmp.numerator >>= 1;
tmp.denominator >>= 1;
}
if (tmp.integer > 0xffffffff || tmp.numerator > 0xffffffff ||
tmp.denominator > 0xffffffff)
return setError(RadioInterface::Failure,error,
"Multi synth calculation - rate parts are too big");
synth.a = (uint32_t)tmp.integer;
synth.b = (uint32_t)tmp.numerator;
synth.c = (uint32_t)tmp.denominator;
synth.r = rValue;
// Pack the registers
packRegs(synth);
return 0;
}
void BrfLibUsbDevice::packRegs(Si5338MultiSynth& synth)
{
uint64_t tmp = (uint64_t)synth.a * synth.c + synth.b;
tmp = tmp * 128 ;
tmp = tmp / synth.c - 512;
synth.p1 = (uint32_t)tmp;
tmp = (uint64_t)synth.b * 128;
tmp = tmp % synth.c;
synth.p2 = (uint32_t)tmp;
synth.p3 = synth.c;
// Set regs
synth.regs[0] = (uint8_t)synth.p1;
synth.regs[1] = (uint8_t)(synth.p1 >> 8);
synth.regs[2] = (uint8_t)((synth.p2 & 0x3f) << 2) | ((synth.p1 >> 16) & 0x3);
synth.regs[3] = (uint8_t)(synth.p2 >> 6);
synth.regs[4] = (uint8_t)(synth.p2 >> 14);
synth.regs[5] = (uint8_t)(synth.p2 >> 22);
synth.regs[6] = (uint8_t)synth.p3;
synth.regs[7] = (uint8_t)(synth.p3 >> 8);
synth.regs[8] = (uint8_t)(synth.p3 >> 16);
synth.regs[9] = (uint8_t)(synth.p3 >> 24);
}
void BrfLibUsbDevice::unpackRegs(Si5338MultiSynth& synth)
{
// Populate
synth.p1 = ((synth.regs[2] & 3) << 16) | (synth.regs[1] << 8) | (synth.regs[0]);
synth.p2 = (synth.regs[5] << 22) | (synth.regs[4] << 14) |
(synth.regs[3] << 6) | ((synth.regs[2] >> 2) & 0x3f);
synth.p3 = ((synth.regs[9] & 0x3f) << 24) | (synth.regs[8] << 16) |
(synth.regs[7] << 8) | (synth.regs[6]);
// c = p3
synth.c = synth.p3;
// a = (p1+512)/128
// NOTE: The +64 is for rounding purposes.
synth.a = (synth.p1 + 512) / 128;
// b = (((p1+512)-128*a)*c + (b % c) + 64)/128
uint64_t tmp = (synth.p1 + 512) - 128 * (uint64_t)synth.a;
tmp = (tmp * synth.c) + synth.p2;
tmp = (tmp + 64) / 128;
synth.b = (uint32_t)tmp;
}
unsigned int BrfLibUsbDevice::lusb2ifaceError(int code)
{
switch (code) {
case LIBUSB_ERROR_ACCESS: // Access denied (insufficient permissions)
case LIBUSB_TRANSFER_ERROR: // Transfer failed
case LIBUSB_ERROR_BUSY: // Resource busy
case LIBUSB_ERROR_INVALID_PARAM: // Invalid parameter
case LIBUSB_ERROR_NO_MEM: // Insufficient memory
case LIBUSB_ERROR_OTHER: // Unknown error
return RadioInterface::Failure;
case LIBUSB_ERROR_TIMEOUT: // Operation timed out
case LIBUSB_TRANSFER_TIMED_OUT: // Transfer timed out
return RadioInterface::Timeout;
case LIBUSB_ERROR_INTERRUPTED: // System call interrupted (perhaps due to signal)
case LIBUSB_TRANSFER_CANCELLED: // Transfer was cancelled
return RadioInterface::Cancelled;
case LIBUSB_TRANSFER_STALL: // For bulk/interrupt endpoints: halt condition detected (endpoint stalled)
// For control endpoints: control request not supported
return RadioInterface::HardwareIOError;
case LIBUSB_ERROR_NOT_FOUND: // Entity not found
case LIBUSB_ERROR_NO_DEVICE: // No such device (it may have been disconnected)
case LIBUSB_TRANSFER_NO_DEVICE: // Device was disconnected
return RadioInterface::HardwareNotAvailable;
case LIBUSB_ERROR_IO: // Input/output error
case LIBUSB_ERROR_PIPE: // Pipe error
return RadioInterface::HardwareIOError;
case LIBUSB_ERROR_OVERFLOW: // Overflow
case LIBUSB_TRANSFER_OVERFLOW: // Device sent more data than requested
return RadioInterface::Failure;
case LIBUSB_ERROR_NOT_SUPPORTED: // Operation not supported or unimplemented on this platform
return RadioInterface::NotSupported;
case LIBUSB_SUCCESS: // Success (no error)
return RadioInterface::NoError;
#if LIBUSB_TRANSFER_COMPLETED != LIBUSB_SUCCESS
case LIBUSB_TRANSFER_COMPLETED: // Transfer completed without error
// Note that this does not indicate that the entire amount of
// requested data was transferred.
return RadioInterface::NoError;
#endif
}
return RadioInterface::Failure;
}
void BrfLibUsbDevice::doClose()
{
//Debugger d(DebugNote,"doClose "," %s [%p]",m_owner->debugName(),m_owner);
m_closing = true;
closeDevice();
clearDeviceList();
m_closing = false;
}
unsigned int BrfLibUsbDevice::setState(BrfDevState& state, String* error)
{
#define BRF_SET_STATE_COND(cond,flags,flag,func) { \
if ((cond) && ((flags & flag) != 0)) { \
unsigned int tmp = func; \
if (tmp) { \
if (fatal) \
return tmp; \
if (!status) { \
error = 0; \
status = tmp; \
} \
} \
} \
flags &= ~flag; \
if (!flags) \
continue;\
}
#define BRF_SET_STATE(flags,flag,func) BRF_SET_STATE_COND(true,flags,flag,func)
#define BRF_SET_STATE_NOERROR(flags,flag,func) { \
if ((flags & flag) != 0) { \
func; \
flags &= ~flag; \
} \
if (!flags) \
continue;\
}
unsigned int status = 0;
BRF_FUNC_CALL_RET(cancelled(error));
XDebug(m_owner,DebugAll,"Set state 0x%x / 0x%x / 0x%x [%p]",
state.m_changed,state.m_txChanged,state.m_rxChanged,m_owner);
bool fatal = (state.m_changed & DevStatAbortOnFail) != 0;
state.m_changed &= ~DevStatAbortOnFail;
// Direction related
for (int i = 0; i < 2; i++) {
bool tx = (i == 0);
unsigned int& f = tx ? state.m_txChanged : state.m_rxChanged;
if (!f)
continue;
BrfDevDirState& s = tx ? state.m_tx : state.m_rx;
BRF_SET_STATE(f,DevStatLpf,internalSetLpf(tx,s.lpf,error));
BRF_SET_STATE_COND(s.lpfBw != 0,f,DevStatLpfBw,
internalSetLpfBandwidth(tx,s.lpfBw,error));
BRF_SET_STATE_COND(s.sampleRate != 0,f,DevStatSampleRate,
internalSetSampleRate(tx,s.sampleRate,error));
BRF_SET_STATE_COND(s.frequency != 0,f,DevStatFreq,
internalSetFrequency(tx,s.frequency,error));
BRF_SET_STATE(f,DevStatVga1,internalSetVga(tx,s.vga1,true,error));
BRF_SET_STATE(f,DevStatVga2,internalSetVga(tx,s.vga2,false,error));
BRF_SET_STATE(f,DevStatDcI,internalSetDcOffset(tx,true,s.dcOffsetI,error));
BRF_SET_STATE(f,DevStatDcQ,internalSetDcOffset(tx,false,s.dcOffsetQ,error));
BRF_SET_STATE(f,DevStatFpgaPhase,internalSetFpgaCorr(tx,CorrFpgaPhase,
s.fpgaCorrPhase,error));
BRF_SET_STATE(f,DevStatFpgaGain,internalSetFpgaCorr(tx,CorrFpgaGain,
s.fpgaCorrGain,error));
}
// Common
while (state.m_changed) {
BRF_SET_STATE(state.m_changed,DevStatLoopback,
internalSetLoopback(state.m_loopback,state.m_loopbackParams,error));
BRF_SET_STATE_NOERROR(state.m_changed,DevStatRxDcAuto,
setRxDcAuto(state.m_rxDcAuto));
BRF_SET_STATE_NOERROR(state.m_changed,DevStatTxPattern,
setTxPattern(state.m_txPattern,state.m_txPatternGain));
break;
}
if (state.m_changed || state.m_txChanged || state.m_rxChanged)
Debug(m_owner,DebugWarn,"Set state incomplete: 0x%x / 0x%x / 0x%x [%p]",
state.m_changed,state.m_txChanged,state.m_rxChanged,m_owner);
return status;
#undef BRF_SET_STATE_COND
#undef BRF_SET_STATE
#undef BRF_SET_STATE_NOERROR
}
// Request changes (synchronous TX). Wait for change
unsigned int BrfLibUsbDevice::setStateSync(String* error)
{
if (m_syncTxStateSet)
return setErrorFail(error,"Sync set state overlapping");
m_syncTxStateCode = 0;
m_syncTxStateSet = true;
unsigned int intervals = threadIdleIntervals(m_syncTout);
unsigned int status = 0;
while (m_syncTxStateSet && !status) {
m_syncSemaphore.lock(Thread::idleUsec());
status = cancelled(error);
if (!status && m_syncTxStateSet && (intervals--) == 0)
status = setErrorTimeout(error,"Sync set state timeout");
}
m_syncTxStateSet = false;
if (status)
return status;
if (!m_syncTxStateCode)
return 0;
return setError(m_syncTxStateCode,error,m_syncTxStateError);
}
void BrfLibUsbDevice::internalDumpDev(String& buf, bool info, bool state,
const char* sep, bool internal, bool fromStatus, bool withHdr)
{
String tmp;
if (state) {
BrfDevDirState& tx = getDirState(true);
BrfDevDirState& rx = getDirState(false);
if (withHdr) {
buf.append("RxVGA1=",sep) << rx.vga1;
buf << sep << "RxVGA2=" << rx.vga2;
buf << sep << "RxDCCorrI=" << rx.dcOffsetI;
buf << sep << "RxDCCorrQ=" << rx.dcOffsetQ;
buf << sep << "TxVGA1=" << tx.vga1;
buf << sep << "TxVGA2=" << tx.vga2;
buf << sep << dumpFloatG(tmp,(double)rx.frequency / 1000000,"RxFreq=","MHz");
if (internal) {
buf << sep << "TxDCCorrI=" << tx.dcOffsetI;
buf << sep << "TxDCCorrQ=" << tx.dcOffsetQ;
}
buf << sep << dumpFloatG(tmp,(double)tx.frequency / 1000000,"TxFreq=","MHz");
buf << sep << "FreqOffset=" << m_freqOffset;
buf << sep << "RxSampRate=" << rx.sampleRate;
buf << sep << "TxSampRate=" << tx.sampleRate;
buf << sep << "RxLpfBw=" << rx.lpfBw;
buf << sep << "TxLpfBw=" << tx.lpfBw;
buf << sep << "RxRF=" << onStr(rx.rfEnabled);
buf << sep << "TxRF=" << onStr(tx.rfEnabled);
if (internal) {
buf << sep << "RxLPF=" << lookup(rx.lpf,s_lpf);
buf << sep << "TxLPF=" << lookup(tx.lpf,s_lpf);
buf << sep << "TxCorrFpgaPhase=" << tx.fpgaCorrPhase;
}
}
else {
buf << "|" << rx.vga1;
buf << "|" << rx.vga2;
buf << "|" << rx.dcOffsetI;
buf << "|" << rx.dcOffsetQ;
buf << "|" << tx.vga1;
buf << "|" << tx.vga2;
buf << "|" << dumpFloatG(tmp,(double)rx.frequency / 1000000,0,"MHz");
buf << "|" << dumpFloatG(tmp,(double)tx.frequency / 1000000,0,"MHz");
buf << "|" << m_freqOffset;
buf << "|" << rx.sampleRate;
buf << "|" << tx.sampleRate;
buf << "|" << rx.lpfBw;
buf << "|" << tx.lpfBw;
buf << "|" << onStr(rx.rfEnabled);
buf << "|" << onStr(tx.rfEnabled);
}
}
if (!info)
return;
if (withHdr) {
buf.append("Address=",sep) << address();
buf << sep << "Serial=" << serial();
buf << sep << "Speed=" << speedStr();
buf << sep << "Firmware=" << fwVerStr();
buf << sep << "FPGA=" << fpgaVerStr();
if (!fromStatus) {
buf.append(fpgaFile()," - ");
buf.append(fpgaMD5()," - MD5: ");
}
buf << sep << "LMS_Ver=" << lmsVersion();
}
else {
if (buf)
buf << "|";
buf << address();
buf << "|" << serial();
buf << "|" << speedStr();
buf << "|" << fwVerStr();
buf << "|" << fpgaVerStr();
buf << "|" << lmsVersion();
}
}
unsigned int BrfLibUsbDevice::internalPowerOn(bool rfLink, bool tx, bool rx, String* error)
{
String e;
unsigned int status = 0;
BrfDevTmpAltSet tmpAltSet(this);
if (rfLink)
status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
else
status = tmpAltSet.set(&e,"Power ON/OFF");
bool warn = (tx != m_state.m_tx.rfEnabled) || (rx != m_state.m_rx.rfEnabled);
while (status == 0) {
if (tx || rx) {
BRF_FUNC_CALL_BREAK(enableTimestamps(true,&e));
if (m_calLms)
BRF_FUNC_CALL_BREAK(calibrateAuto(&e));
}
BRF_FUNC_CALL_BREAK(enableRf(true,tx,false,&e));
BRF_FUNC_CALL_BREAK(enableRf(false,rx,false,&e))
if (tx || rx) {
String extra;
if (!(tx && rx))
extra << ", " << brfDir(tx) << " only";
Debug(m_owner,DebugNote,"Powered ON the radio%s [%p]",extra.safe(),m_owner);
}
else if (warn)
Debug(m_owner,DebugNote,"Powered OFF the radio [%p]",m_owner);
return 0;
}
if (!warn)
return 0;
e.printf(1024,"Power %s failed: %s",((tx || rx) ? "ON" :"OFF"),e.c_str());
return showError(status,e,0,error);
}
// Send an array of samples waiting to be transmitted
unsigned int BrfLibUsbDevice::send(uint64_t ts, float* data, unsigned int samples,
float* powerScale)
{
#ifndef DEBUG_DEVICE_TX
XDebug(m_owner,DebugAll,"send(" FMT64U ",%p,%u) [%p]",ts,data,samples,m_owner);
#endif
BrfDevIO& io = m_txIO;
BrfDevDirState& dirState = m_state.m_tx;
if (!io.startTime)
io.startTime = Time::now();
if (io.dataDumpParams || io.upDumpParams)
updateIODump(io);
if (!(data && samples))
return 0;
#ifdef DEBUG_DEVICE_TX
Debugger debug(DebugInfo,"BrfLibUsbDevice::send()",
" %s: ts=" FMT64U " (expected=" FMT64U ") samples=%u [%p]",
m_owner->debugName(),ts,io.timestamp,samples,m_owner);
#endif
if (io.upDumpFile.valid() && !(checkDbgInt(io.upDump) &&
io.upDumpFile.write(ts,data,samplesf2bytes(samples),owner())))
io.upDumpFile.terminate(owner());
// Check timestamp
if (io.timestamp != ts) {
if (m_calibrateStatus != Calibrating && io.timestamp &&
m_owner->debugAt(DebugAll)) {
String s;
s << "(our=" << io.timestamp << " requested=" << ts << ")";
if (io.crtBuf || io.crtBufSampOffs)
s << ", dropping previous data " <<
(io.crtBuf * io.bufSamples + io.crtBufSampOffs) << " samples";
Debug(m_owner,DebugAll,"TX: timestamps don't match %s [%p]",s.c_str(),m_owner);
}
io.resetBufPos();
io.timestamp = ts;
}
const long* ampTable = 0;
if (m_ampTableUse)
ampTable = m_ampTable;
float scale = 0;
float* scaleI = &m_wrPowerScaleI;
float* scaleQ = &m_wrPowerScaleQ;
int16_t* maxI = &m_wrMaxI;
int16_t* maxQ = &m_wrMaxQ;
if (m_txPowerBalanceChanged) {
m_txPowerBalanceChanged = false;
m_wrPowerScaleI = m_txPowerScaleI * s_sampleEnergize;
m_wrPowerScaleQ = m_txPowerScaleQ * s_sampleEnergize;
m_wrMaxI = sampleScale(m_txPowerScaleI,s_sampleEnergize);
m_wrMaxQ = sampleScale(m_txPowerScaleQ,s_sampleEnergize);
if (dirState.showPowerBalanceChange == 0)
Debug(m_owner,DebugInfo,"TX using power scale I=%g Q=%g maxI=%d maxQ=%d [%p]",
m_wrPowerScaleI,m_wrPowerScaleQ,m_wrMaxI,m_wrMaxQ,m_owner);
}
if (powerScale && m_wrPowerScaleI == s_sampleEnergize) {
scale = *powerScale * s_sampleEnergize;
scaleI = scaleQ = &scale;
maxI = maxQ = &s_sampleEnergize;
}
if (m_txPatternChanged)
sendTxPatternChanged();
unsigned int clamped = 0;
String e;
unsigned int status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
unsigned int reqSend = samples;
while (!status) {
while (samples && io.crtBuf < io.buffers) {
unsigned int avail = 0;
int16_t* start = io.crtBufSamples(avail);
if (avail > samples)
avail = samples;
// New buffer: set the timestamp
if (!io.crtBufSampOffs)
io.setBufTs(io.crtBuf,io.timestamp);
samples -= avail;
#ifdef DEBUG_DEVICE_TX
Debugger loopDbg(DebugAll,"TX: processing buffer",
" %u/%u ts=" FMT64U " avail=%u remains=%u [%p]",
io.crtBuf + 1,io.buffers,io.timestamp,avail,samples,m_owner);
#endif
io.crtBufSampOffs += avail;
io.timestamp += avail;
if (m_txPatternBuffer.length() == 0) {
brfCopyTxData(start,data,avail,*scaleI,*maxI,*scaleQ,*maxQ,clamped,ampTable);
data += avail * 2;
}
else
sendCopyTxPattern(start,avail,*scaleI,*maxI,*scaleQ,*maxQ,clamped,ampTable);
if (io.crtBufSampOffs >= io.bufSamples)
io.advanceBuffer();
#ifdef XDEBUG
if (avail) {
float sum = 0.0F;
for (unsigned i=0; i<avail*2; i++) {
sum += start[i]*start[i];
}
const float rms = ::sqrtf(sum/avail);
const float dB = 20.0F*log10f((rms+0.5F)/2048.0F);
XDebug(m_owner,DebugAll,"energized ouput RMS level %f in linear amplitude (%f dB) [%p]",
rms, dB, this);
}
#endif
}
unsigned int nBuf = io.crtBuf;
unsigned int oldBufSampOffs = nBuf ? io.crtBufSampOffs : 0;
if (m_syncTxStateSet) {
m_syncTxStateCode = setState(m_syncTxState,&m_syncTxStateError);
m_syncTxState.m_tx.m_timestamp = ts + nBuf * io.bufSamples;
m_syncTxStateSet = false;
m_syncSemaphore.unlock();
}
if (nBuf < m_minBufsSend)
break;
if (checkDbgInt(io.checkTs))
ioBufCheckTs(true,nBuf);
else
io.lastTs = io.timestamp;
unsigned int nPrint = checkDbgInt(io.showBuf,nBuf);
if (nPrint)
printIOBuffer(true,"SEND",-1,nPrint);
if (io.dataDumpFile.valid())
dumpIOBuffer(io,nBuf);
status = syncTransfer(EpSendSamples,io.bufStart(0),io.bufLen * nBuf,&e);
// Reset buffer to start
io.resetBufPos();
// Copy partial buffer from end
if (oldBufSampOffs) {
#ifdef DEBUG_DEVICE_TX
Debug(m_owner,DebugMild,"TX: copying buffer %u to start [%p]",nBuf,m_owner);
#endif
::memcpy(io.bufStart(0),io.bufStart(nBuf),io.hdrLen + samplesi2bytes(oldBufSampOffs));
io.crtBufSampOffs = oldBufSampOffs;
}
if (status)
break;
io.transferred += nBuf * io.bufSamples;
}
if (status == 0) {
if (clamped) {
float percent = 100 * (float)clamped / reqSend;
Debug(m_owner,(percent < m_warnClamped) ? DebugAll : DebugNote,
"Output buffer clamped %u/%u (%.2f%%) [%p]",clamped,reqSend,percent,m_owner);
}
if (samples)
Debug(DebugFail,"Exiting with non 0 samples");
}
else if (status != RadioInterface::Cancelled)
Debug(m_owner,DebugNote,"Send failed (TS=" FMT64U "): %s [%p]",
io.timestamp,e.c_str(),m_owner);
return status;
}
void BrfLibUsbDevice::sendTxPatternChanged()
{
Lock lck(m_dbgMutex);
if (!m_txPatternChanged)
return;
m_txPatternChanged = false;
m_txPatternBuffer.steal(m_txPattern);
if (m_txPatternBuffer.length())
Debug(m_owner,DebugInfo,
"Using send pattern '%s' %u samples at TS=" FMT64U " [%p]",
m_state.m_txPattern.substr(0,50).c_str(),m_txPatternBuffer.length(),
m_txIO.timestamp,m_owner);
m_txPatternBufPos = m_txPatternBuffer.length();
}
void BrfLibUsbDevice::sendCopyTxPattern(int16_t* buf, unsigned int avail,
float scaleI, int16_t maxI, float scaleQ, int16_t maxQ, unsigned int& clamped,
const long* ampTable)
{
while (avail) {
if (m_txPatternBufPos == m_txPatternBuffer.length())
m_txPatternBufPos = 0;
unsigned int cp = m_txPatternBuffer.length() - m_txPatternBufPos;
if (cp > avail)
cp = avail;
float* b = (float*)m_txPatternBuffer.data(m_txPatternBufPos);
avail -= cp;
m_txPatternBufPos += cp;
brfCopyTxData(buf,b,cp,scaleI,maxI,scaleQ,maxQ,clamped,ampTable);
}
}
// Receive data from the Rx interface of the bladeRF device
// Remember: a sample is an I/Q pair
unsigned int BrfLibUsbDevice::recv(uint64_t& ts, float* data, unsigned int& samples,
String* error)
{
#ifndef DEBUG_DEVICE_RX
XDebug(m_owner,DebugAll,"recv(" FMT64U ",%p,%u) [%p]",ts,data,samples,m_owner);
#endif
BrfDevIO& io = m_rxIO;
if (!io.startTime)
io.startTime = Time::now();
if (io.dataDumpParams || io.upDumpParams)
updateIODump(io);
if (!(data && samples))
return 0;
#ifdef DEBUG_DEVICE_RX
Debugger debug(DebugInfo,"BrfLibUsbDevice::recv()",
" %s: ts=" FMT64U " samples=%u data=(%p) [%p]",
m_owner->debugName(),ts,samples,data,m_owner);
#endif
unsigned int samplesCopied = 0;
unsigned int samplesLeft = samples;
float* cpDest = data;
uint64_t crtTs = ts;
String e;
unsigned int status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
unsigned int nSamplesInPast = 0;
while (!status) {
while (samplesLeft && io.crtBuf < io.buffers) {
// Retrieve buffer timestamp
uint64_t bufTs = io.bufTs(io.crtBuf);
if (io.crtBufSampOffs)
bufTs += io.crtBufSampOffs;
int64_t resync = (io.newBuffer && bufTs != m_rxTimestamp) ?
(int64_t)(bufTs - m_rxTimestamp) : 0;
#ifdef DEBUG_DEVICE_RX
String deltaStr;
if (resync)
deltaStr << " " << (resync > 0 ? "future=" : "past=") <<
(resync > 0 ? resync : -resync);
Debugger loopDbg(DebugAll,"RX: processing buffer",
" %u/%u rx_ts=" FMT64U " (resync_ts=" FMT64U ") "
"ts=" FMT64U " crt_ts=" FMT64U "%s [%p]",
io.crtBuf + 1,io.buffers,m_rxTimestamp,m_rxResyncCandidate,
bufTs,crtTs,deltaStr.safe(),m_owner);
#endif
if (resync) {
if ((resync > -1000 && resync < 1000) || bufTs == m_rxResyncCandidate) {
Debug(m_owner,bufTs > m_silenceTs ? DebugNote : DebugAll,
"RX: timestamp adjusted by " FMT64 " to " FMT64U " [%p]",
resync,bufTs,m_owner);
m_rxTimestamp = bufTs;
m_rxResyncCandidate = 0;
}
else {
Debug(m_owner,bufTs > m_silenceTs ? DebugWarn : DebugAll,
"RX: timestamp jumped by " FMT64 " to " FMT64U " in buffer %u/%u [%p]",
resync,m_rxTimestamp,io.crtBuf + 1,io.buffers,m_owner);
m_rxResyncCandidate = bufTs;
}
}
io.newBuffer = false;
unsigned int avail = 0;
int16_t* start = io.crtBufSamples(avail);
if (avail > samplesLeft)
avail = samplesLeft;
// Check timestamp
if (m_rxTimestamp > crtTs) {
// Buffer timestamp is in the future
#ifdef DEBUG_DEVICE_RX
if (crtTs)
Debug(m_owner,DebugNote,
"RX: timestamp in future in buffer %u/%u requested="
FMT64U " found=" FMT64U " [%p]",
io.crtBuf + 1,io.buffers,crtTs,m_rxTimestamp,m_owner);
#endif
// Pad with 0
uint64_t delta = m_rxTimestamp - crtTs;
if (delta > samplesLeft)
delta = samplesLeft;
crtTs += delta;
samplesLeft -= delta;
samplesCopied += delta;
::memset(cpDest,0,2 * delta * sizeof(float));
cpDest += 2 * delta;
#ifdef DEBUG_DEVICE_RX
Debug(m_owner,DebugAll,
"RX: zeroed %u samples status=%u/%u remains=%u [%p]",
(unsigned int)delta,samplesCopied,samples,samplesLeft,m_owner);
#endif
if (!samplesLeft)
break;
if (avail > samplesLeft)
avail = samplesLeft;
}
else if (m_rxTimestamp < crtTs) {
// Timestamp in the past: check if can use some data, skip buffer
unsigned int skipSamples = avail;
uint64_t delta = crtTs - m_rxTimestamp;
if (delta < skipSamples)
skipSamples = delta;
#ifdef DEBUG_DEVICE_RX
Debug(m_owner,DebugNote,
"RX: skipping %u/%u samples in buffer %u/%u:"
" timestamp in the past by " FMT64U " [%p]",
skipSamples,avail,io.crtBuf + 1,io.buffers,delta,m_owner);
#endif
avail -= skipSamples;
nSamplesInPast += skipSamples;
io.crtBufSampOffs += skipSamples;
m_rxTimestamp += skipSamples;
if (m_rxResyncCandidate)
m_rxResyncCandidate += skipSamples;
if (io.crtBufSampOffs >= io.bufSamples) {
io.advanceBuffer();
continue;
}
}
// We have some valid data: reset samples in the past counter
if (avail)
nSamplesInPast = 0;
int16_t* last = start + avail * 2;
// Copy data
static const float s_mul = 1.0 / 2048;
while (start != last) {
*cpDest++ = *start++ * s_mul;
*cpDest++ = *start++ * s_mul;
}
samplesCopied += avail;
samplesLeft -= avail;
m_rxTimestamp += avail;
if (m_rxResyncCandidate)
m_rxResyncCandidate += avail;
#ifdef DEBUG_DEVICE_RX
Debug(m_owner,DebugAll,
"RX: copied %u samples from buffer %u/%u status=%u/%u remains=%u [%p]",
avail,io.crtBuf + 1,io.buffers,samplesCopied,
samples,samplesLeft,m_owner);
#endif
// Advance buffer offset, advance the buffer if we used all data
io.crtBufSampOffs += avail;
if (io.crtBufSampOffs >= io.bufSamples) {
io.advanceBuffer();
crtTs += avail;
}
}
if (!samplesLeft)
break;
if (nSamplesInPast > m_rxTsPastSamples) {
// Don't signal error if we have some valid data
// This will allow the upper layer to update timestamps
// Read operation may fail on subsequent reads
if (!samplesCopied) {
e = "Too much data in the past";
status = RadioInterface::Failure;
}
break;
}
status = syncTransfer(EpReadSamples,io.bufStart(0),io.buffer.length(),&e);
if (status)
break;
io.resetBufPos();
if (io.dataDumpFile.valid())
dumpIOBuffer(io,io.buffers);
io.transferred += io.buffers * io.bufSamples;
io.fixEndian();
unsigned int nPrint = checkDbgInt(io.showBuf,io.buffers);
if (nPrint)
printIOBuffer(false,"RECV",-1,nPrint);
if (m_rxAlterData)
rxAlterData(true);
if (checkDbgInt(io.checkLimit))
ioBufCheckLimit(false);
if (checkDbgInt(io.checkTs))
ioBufCheckTs(false);
if (m_state.m_rxDcAuto || m_rxShowDcInfo)
computeRx(crtTs);
if (m_rxAlterData)
rxAlterData(false);
}
samples = samplesCopied;
#ifdef DEBUG_DEVICE_RX
Debug(m_owner,DebugAll,
"BrfLibUsbDevice::recv() exiting status=%u ts=" FMT64U " samples=%u [%p]",
status,ts,samples,m_owner);
#endif
if (io.captureBuf)
captureHandle(io,data,samples,ts,status,&e);
if (status == 0) {
m_rxIO.timestamp = ts;
if (io.upDumpFile.valid() && !(checkDbgInt(io.upDump) &&
io.upDumpFile.write(ts,data,samplesf2bytes(samples),owner())))
io.upDumpFile.terminate(owner());
}
else if (error)
return showError(status,e,"Recv failed",error);
else if (status != RadioInterface::Cancelled)
Debug(m_owner,DebugNote,"Recv failed: %s [%p]",e.c_str(),m_owner);
return status;
}
void BrfLibUsbDevice::captureHandle(BrfDevIO& io, const float* buf, unsigned int samples,
uint64_t ts, unsigned int status, const String* error)
{
//#define BRF_CAPTURE_HANDLE_TRACE
Lock lck(io.captureMutex);
if (!io.captureBuf)
return;
bool done = false;
if (!status) {
// Handle data
unsigned int cp = 0;
unsigned int bufOffs = 0;
uint64_t tsCapture = io.captureTs + io.captureOffset;
unsigned int samplesLeft = io.captureSamples - io.captureOffset;
#ifdef BRF_CAPTURE_HANDLE_TRACE
Output("CAPTURE[%s] (%u) " FMT64U "/%u (" FMT64U ") IO: " FMT64U
"/%u (last=" FMT64U ") delta=" FMT64 " samplesLeft=%u",
brfDir(io.tx()),io.captureSamples,io.captureTs,io.captureOffset,tsCapture,
ts,samples,(ts + samples),(int64_t)(ts + samples - tsCapture),samplesLeft);
#endif
if (tsCapture == ts)
cp = (samplesLeft < samples) ? samplesLeft : samples;
else {
uint64_t lastTs = ts + samples;
bool useData = false;
bool reset = true;
if (tsCapture > ts) {
useData = !io.captureOffset && (lastTs > tsCapture);
reset = !useData && (lastTs >= tsCapture);
}
if (useData) {
cp = (unsigned int)(lastTs - tsCapture);
if (cp > samples)
cp = samples;
if (cp > samplesLeft)
cp = samplesLeft;
if (cp)
bufOffs = samples - cp;
}
else if (reset) {
// Reset buffer (avoid data gaps)
io.captureTs = lastTs;
io.captureOffset = 0;
#ifdef BRF_CAPTURE_HANDLE_TRACE
Output(" reset TS=" FMT64U,io.captureTs);
#endif
}
}
if (cp) {
unsigned int nCopy = samplesf2bytes(cp);
::memcpy(io.captureBuf + 2 * io.captureOffset,buf + 2 * bufOffs,nCopy);
io.captureOffset += cp;
samplesLeft -= cp;
#ifdef BRF_CAPTURE_HANDLE_TRACE
Output(" cp=%u from=%u offset=%u samplesLeft=%u",
cp,bufOffs,io.captureOffset,samplesLeft);
#endif
}
if (!samplesLeft) {
done = true;
io.captureStatus = 0;
io.captureError.clear();
}
}
else {
io.captureStatus = status;
if (!TelEngine::null(error))
io.captureError = (io.tx() ? "Send failed: " : "Recv failed: ") + *error;
else
io.captureError.clear();
done = true;
}
if (!done)
return;
io.captureBuf = 0;
io.captureSemaphore.unlock();
}
unsigned int BrfLibUsbDevice::internalSetSampleRate(bool tx, uint32_t value,
String* error)
{
String e;
unsigned int status = 0;
if (value <= m_radioCaps.maxSampleRate)
status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
else {
status = RadioInterface::InsufficientSpeed;
e << "insufficient speed required=" << value << " max=" << m_radioCaps.maxSampleRate;
}
while (!status) {
Si5338MultiSynth synth;
BrfRationalRate rate;
rate.integer = value;
rate.numerator = 0; // keeping the numerator and the donominator
rate.denominator = 1; // for future use
// Enforce minimum sample rate
reduceRational(rate);
if (rate.integer < BRF_SAMPLERATE_MIN)
Debug(m_owner,DebugConf,
"Requested %s sample rate %u is smaller than allowed minimum value [%p]",
brfDir(tx),value,m_owner);
// Setup the multisynth enables and index
synth.enable = 0x01;
synth.index = 1;
if (tx) {
synth.enable |= 0x02;
synth.index = 2;
}
// Update the base address register
synth.base = 53 + synth.index * 11;
// Calculate multisynth values
BRF_FUNC_CALL_BREAK(calcMultiSynth(synth,rate,&e));
// Program it to the part
// Write out the enables
uint8_t val = 0;
BRF_FUNC_CALL_BREAK(getSi5338(36 + synth.index,val,&e));
val &= ~(7);
val |= synth.enable;
BRF_FUNC_CALL_BREAK(setSi5338(36 + synth.index,val,&e));
// Write out the registers
for (int i = 0 ; i < 10; i++)
BRF_FUNC_CALL_BREAK(setSi5338(synth.base + i,*(synth.regs + i),&e));
if (status)
break;
// Calculate r_power from c_count
uint8_t rPower = 0;
uint8_t rCount = synth.r >> 1;
while (rCount) {
rCount >>= 1;
rPower++;
}
// Set the r value to the log2(r_count) to match Figure 18
val = 0xc0;
val |= (rPower<<2);
BRF_FUNC_CALL_BREAK(setSi5338(31 + synth.index,val,&e));
if (getDirState(tx).sampleRate != value) {
getDirState(tx).sampleRate = value;
Debug(m_owner,DebugInfo,"%s samplerate set to %u [%p]",
brfDir(tx),value,m_owner);
// Signal sample rate change to internal TX/RX
Lock lck(m_threadMutex);
if (tx)
m_internalIoTxRate = value;
else
m_internalIoRxRate = value;
m_internalIoRateChanged = true;
}
if (!tx) {
unsigned int samplesMs = (value + 999) / 1000;
// Calculate RX samples allowed to be in the past
m_rxTsPastSamples = m_rxTsPastIntervalMs * samplesMs;
// Calculate RX timestamp silence
m_silenceTs = m_silenceTimeMs * samplesMs;
}
BrfDevIO& io = getIO(tx);
bool first = !io.firstBufsThres.sampleRate;
if (first) {
io.firstBufsThres.sampleRate = value;
io.firstBufsThres.bufferedSamples = totalSamples(tx);
io.firstBufsThres.txMinBufs = m_minBufsSend;
}
// Check for I/O buffers info
const BrfBufsThreshold* t = BrfBufsThreshold::findThres(m_bufThres,value);
if (!t && !first)
// No threshold set: rollback to first values
t = &io.firstBufsThres;
if (t)
initBuffers(&tx,t->bufferedSamples,t->txMinBufs);
return 0;
}
e.printf(1024,"Failed to set %s samplerate %u: %s",brfDir(tx),value,e.c_str());
return showError(status,e,0,error);
}
// Update FPGA (load, get version)
unsigned int BrfLibUsbDevice::updateFpga(const NamedList& params)
{
const String& oper = params[YSTRING("fpga_load")];
int load = 0;
if (!oper)
load = 1;
else if (oper == YSTRING("auto")) {
unsigned int code = checkFpga();
if (code == RadioInterface::NoError)
load = -1;
else {
load = 1;
if (code != RadioInterface::NotInitialized)
Debug(m_owner,DebugNote,"Forcing FPGA load (check failure) [%p]",m_owner);
}
}
else
load = oper.toBoolean(true) ? 1 : 0;
if (load > 0)
Debug(m_owner,DebugAll,"Updating FPGA [%p]",m_owner);
else
Debug(m_owner,DebugInfo,"Skipping FPGA load: %s [%p]",
(load ? "checked, already loaded" : "disabled by config"),m_owner);
m_devFpgaFile.clear();
m_devFpgaVerStr.clear();
m_devFpgaMD5.clear();
String e;
unsigned int status = 0;
while (load > 0) {
MD5 md5;
String val;
status = getCalField(val,"B","FPGA size",&e);
if (status)
break;
String fName;
if (val == YSTRING("115") || val == YSTRING("40"))
fName = params.getValue("fpga_file_" + val,
"${sharedpath}/data/hostedx" + val + ".rbf");
else {
e << "Unknown FPGA size value '" << val << "'";
status = RadioInterface::Failure;
break;
}
Engine::runParams().replaceParams(fName);
const char* oper = 0;
// Read the FPGA contents
File f;
DataBlock buf;
if (f.openPath(fName,false,true)) {
int64_t len = f.length();
if (len > 0) {
buf.assign(0,len);
int rd = f.readData(buf.data(),buf.length());
if (rd != len)
oper = "read";
}
else if (f.error())
oper = "detect length";
}
else
oper = "open";
if (oper) {
status = RadioInterface::Failure;
String tmp;
Thread::errorString(tmp,f.error());
e << "File '" << fName << "' " << oper << " failed (" <<
f.error() << " '" << tmp << "')";
break;
}
md5 << buf;
Debug(m_owner,DebugAll,"Loading FPGA from '%s' len=%u [%p]",
fName.c_str(),buf.length(),m_owner);
// Write the FPGA
BrfDevTmpAltSet tmpAltSet(this,BRF_ALTSET_FPGA,status,&e,"FPGA load");
if (status)
break;
status = vendorCommand0_4(BRF_USB_CMD_BEGIN_PROG,LIBUSB_ENDPOINT_IN,&e);
if (status == 0) {
status = lusbBulkTransfer(BRF_ENDP_TX_CTRL,buf.data(0),buf.length(),
0,&e,3 * m_bulkTout);
if (status == 0) {
status = vendorCommand0_4(BRF_USB_CMD_QUERY_FPGA_STATUS,
LIBUSB_ENDPOINT_IN,&e);
if (status)
e = "Failed to end FPGA programming - " + e;
}
else
e = "Failed to send FPGA image - " + e;
}
else
e = "Failed to start FPGA programming - " + e;
tmpAltSet.restore();
status = restoreAfterFpgaLoad(&e);
if (status == 0) {
m_devFpgaFile = fName;
m_devFpgaMD5 = md5.hexDigest();
Debug(m_owner,DebugAll,"Loaded FPGA from '%s' [%p]",fName.c_str(),m_owner);
}
break;
}
if (status) {
Debug(m_owner,DebugWarn,"Failed to load FPGA: %s [%p]",e.c_str(),m_owner);
return status;
}
BrfDevTmpAltSet tmpAltSet(this,status,&e,"FPGA version get");
if (status)
return 0;
uint32_t ver = 0;
if (0 == gpioRead(0x0c,ver,4,&e))
ver2str(m_devFpgaVerStr,ver);
else
Debug(m_owner,DebugNote,"Failed to retrieve FPGA version: %s [%p]",
e.c_str(),m_owner);
return 0;
}
unsigned int BrfLibUsbDevice::internalSetFpgaCorr(bool tx, int corr, int16_t value,
String* error, int lvl)
{
XDebug(m_owner,DebugAll,"internalSetFpgaCorr(%u,%d,%d) [%p]",tx,corr,value,m_owner);
String e;
unsigned int status = 0;
int orig = value;
uint8_t addr = 0;
int* old = 0;
BrfDevDirState& io = getDirState(tx);
bool setBoard = true;
if (corr == CorrFpgaGain) {
old = &io.fpgaCorrGain;
addr = fpgaCorrAddr(tx,false);
if (tx && m_txGainCorrSoftware) {
// Because FPGA Gain cal is broken in older images, we fake it in software.
// We should probably just keep faking it, even in newer FPGA images.
const float bal = 1 + 0.1*(((float)orig) / BRF_FPGA_CORR_MAX);
status = internalSetTxIQBalance(false,bal);
setBoard = false;
}
else {
orig = clampInt(orig,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX,"FPGA GAIN",lvl);
value = orig + BRF_FPGA_CORR_MAX;
}
}
else if (corr == CorrFpgaPhase) {
old = &io.fpgaCorrPhase;
orig = clampInt(orig,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX,"FPGA PHASE",lvl);
value = orig;
addr = fpgaCorrAddr(tx,true);
}
else
status = setUnkValue(e,0,"FPGA corr value " + String(corr));
if (!status) {
if (setBoard)
status = gpioWrite(addr,value,2,&e);
if (!status) {
if (old) {
if (io.showFpgaCorrChange == 0 && *old != orig)
Debug(m_owner,DebugInfo,"%s FPGA corr %s %s to %d (reg %d) [%p]",
brfDir(tx),lookup(corr,s_corr),(setBoard ? "set" : "faked"),
orig,value,m_owner);
*old = orig;
}
return 0;
}
}
e.printf(1024,"Failed to %s %s FPGA corr %s to %d (from %d) - %s [%p]",
(setBoard ? "set" : "fake"),brfDir(tx),lookup(corr,s_corr),
value,orig,e.c_str(),m_owner);
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::internalGetFpgaCorr(bool tx, int corr, int16_t* value,
String* error)
{
String e;
unsigned int status = 0;
uint8_t addr = 0;
int* update = 0;
BrfDevDirState& io = getDirState(tx);
if (corr == CorrFpgaGain) {
if (tx && m_txGainCorrSoftware) {
if (value)
*value = io.fpgaCorrGain;
return 0;
}
update = &io.fpgaCorrGain;
addr = fpgaCorrAddr(tx,false);
}
else if (corr == CorrFpgaPhase) {
update = &io.fpgaCorrPhase;
addr = fpgaCorrAddr(tx,true);
}
else
status = setUnkValue(e,0,"FPGA corr value " + String(corr));
if (!status) {
uint32_t u = 0;
status = gpioRead(addr,u,2,&e);
if (status == 0) {
int v = (int)u;
if (corr == CorrFpgaGain)
v -= BRF_FPGA_CORR_MAX;
if (value)
*value = v;
XDebug(m_owner,DebugAll,"Got %s FPGA corr %s %d [%p]",
brfDir(tx),lookup(corr,s_corr),v,m_owner);
if (update)
*update = v;
return 0;
}
}
e.printf(1024,"Failed to retrieve %s FPGA corr %s - %s [%p]",
brfDir(tx),lookup(corr,s_corr),e.c_str(),m_owner);
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::internalSetTxVga(int vga, bool preMixer, String* error)
{
String e;
unsigned int status = 0;
BrfDevTmpAltSet tmpAltSet(this,status,&e,"TX VGA set");
while (status == 0) {
uint8_t addr = lmsVgaAddr(true,preMixer);
uint8_t data = 0;
BRF_FUNC_CALL_BREAK(lmsRead(addr,data,&e));
if (preMixer) {
vga = clampInt(vga,BRF_TXVGA1_GAIN_MIN,BRF_TXVGA1_GAIN_MAX,"TX VGA1");
data = (uint8_t)((vga - BRF_TXVGA1_GAIN_MIN) & 0x1f);
}
else {
vga = clampInt(vga,BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX,"TX VGA2");
data &= ~0xf8;
data |= (uint8_t)(vga << 3);
}
BRF_FUNC_CALL_BREAK(lmsWrite(addr,data,&e));
if (preMixer)
m_state.m_tx.vga1Changed = true;
int& old = preMixer ? m_state.m_tx.vga1 : m_state.m_tx.vga2;
if (old != vga) {
old = vga;
Debug(m_owner,DebugInfo,"TX VGA%c set to %ddB (0x%x) [%p]",
mixer(preMixer),vga,data,m_owner);
if (!preMixer)
internalSetTxIQBalance(true);
}
return 0;
}
e.printf(1024,"Failed to set TX VGA%c to from %d: %s",mixer(preMixer),vga,e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::internalGetTxVga(int* vga, bool preMixer, String* error)
{
uint8_t data = 0;
String e;
int v = 0;
unsigned int status = lmsRead(lmsVgaAddr(true,preMixer),data,&e);
if (status == 0) {
if (preMixer) {
v = (int)(data & 0x1f) + BRF_TXVGA1_GAIN_MIN;
m_state.m_tx.vga1 = v;
}
else {
v = (data >> 3) & 0x1f;
if (v > BRF_TXVGA2_GAIN_MAX)
v = BRF_TXVGA2_GAIN_MAX;
m_state.m_tx.vga2 = v;
}
if (vga)
*vga = v;
XDebug(m_owner,DebugAll,"Got TX VGA%c %ddB (0x%x) [%p]",
mixer(preMixer),v,data,m_owner);
return 0;
}
e.printf(1024,"Failed to retrieve TX VGA%c: %s",mixer(preMixer),e.c_str());
return showError(status,e,0,error);
}
// Enable or disable the pre/post mixer gain on the receive side
unsigned int BrfLibUsbDevice::internalEnableRxVga(bool on, bool preMixer, String* error)
{
XDebug(m_owner,DebugAll,"internalEnableRxVga(%u,%u) [%p]",on,preMixer,m_owner);
String e;
unsigned int status = 0;
BrfDevTmpAltSet tmpAltSet(this,status,&e,"Enable RX VGA");
while (status == 0) {
uint8_t addr = preMixer ? 0x7d : 0x64;
uint8_t data = 0;
BRF_FUNC_CALL_BREAK(lmsRead(addr,data,&e));
bool old = false;
if (preMixer) {
old = (data & 0x08) == 0;
if (on)
data &= ~0x08;
else
data |= 0x08;
}
else {
old = (data & 0x02) != 0;
if (on)
data |= 0x02;
else
data &= ~0x02;
}
BRF_FUNC_CALL_BREAK(lmsWrite(addr,data,&e));
if (old != on)
Debug(m_owner,DebugInfo,"%s RX VGA%c [%p]",
enabledStr(on),mixer(preMixer),m_owner);
return 0;
}
e.printf(1024,"Failed to %s RX VGA%c: %s",enableStr(on),mixer(preMixer),e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::internalSetRxVga(int vga, bool preMixer, String* error)
{
String e;
unsigned int status = 0;
BrfDevTmpAltSet tmpAltSet(this,status,&e,"RX VGA set");
while (status == 0) {
uint8_t addr = lmsVgaAddr(false,preMixer);
uint8_t data = 0;
BRF_FUNC_CALL_BREAK(lmsRead(addr,data,&e));
int orig = vga;
bool changed = false;
if (preMixer) {
vga = clampInt(vga,BRF_RXVGA1_GAIN_MIN,BRF_RXVGA1_GAIN_MAX,"RX VGA1");
data = (uint8_t)((data & ~0x7f) | s_rxvga1_set[vga]);
BRF_FUNC_CALL_BREAK(lmsWrite(addr,data,&e));
changed = (m_state.m_rx.vga1 != vga);
m_state.m_rx.vga1 = vga;
}
else {
vga = clampInt(vga / 3 * 3,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX,"RX VGA2");
data = (uint8_t)((data & ~0x1f) | (vga / 3));
BRF_FUNC_CALL_BREAK(lmsWrite(addr,data,&e));
changed = (m_state.m_rx.vga2 != vga);
m_state.m_rx.vga2 = vga;
m_rxDcOffsetMax = (int)brfRxDcOffset(clampInt(orig,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX));
}
if (changed)
Debug(m_owner,DebugInfo,"RX VGA%c set to %ddB 0x%x (from %d) [%p]",
mixer(preMixer),vga,data,orig,m_owner);
return 0;
}
e.printf(1024,"Failed to set RX VGA%c to %d: %s",mixer(preMixer),vga,e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::internalGetRxVga(int* vga, bool preMixer, String* error)
{
uint8_t data = 0;
String e;
unsigned int status = lmsRead(lmsVgaAddr(false,preMixer),data,&e);
if (status == 0) {
int v = 0;
if (preMixer) {
int idx = (data & 0x7f);
m_state.m_rx.vga1 = v = s_rxvga1_get[idx < 121 ? idx : 120];
}
else {
m_state.m_rx.vga2 = v = (data & 0x1f) * 3;
m_rxDcOffsetMax = (int)brfRxDcOffset(clampInt(v,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX));
}
XDebug(m_owner,DebugAll,"Got RX VGA%c %ddB (reg=0x%x) [%p]",
mixer(preMixer),v,data,m_owner);
if (vga)
*vga = v;
return 0;
}
e.printf(1024,"Failed to retrieve RX VGA%c: %s",mixer(preMixer),e.c_str());
return showError(status,e,0,error);
}
// Set pre and post mixer value
unsigned int BrfLibUsbDevice::internalSetGain(bool tx, int val, int* newVal, String* error)
{
int vga1 = 0;
if (tx) {
vga1 = (m_state.m_tx.vga1Changed && m_state.m_tx.vga1 >= BRF_TXVGA1_GAIN_MIN) ?
m_state.m_tx.vga1 : BRF_TXVGA1_GAIN_DEF;
val = clampInt(val + BRF_TXVGA2_GAIN_MAX,BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX);
}
else {
vga1 = (m_state.m_rx.vga1 > BRF_RXVGA1_GAIN_MAX) ?
BRF_RXVGA1_GAIN_MAX : m_state.m_rx.vga1;
val = clampInt(val,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX);
}
unsigned int status = internalSetVga(tx,vga1,true,error);
if (!status)
status = internalSetVga(tx,val,false,error);
if (status == 0 && newVal) {
*newVal = val;
if (tx)
*newVal -= BRF_TXVGA2_GAIN_MAX;
}
return status;
}
unsigned int BrfLibUsbDevice::internalSetTxIQBalance(bool newGain, float newBalance,
const char* param)
{
bool dbg = true;
if (!newGain) {
if (newBalance <= 0 || newBalance >= 2) {
if (!param) {
Debug(m_owner,DebugNote,
"Failed to set power balance to %g expected interval (0..2) [%p]",
newBalance,m_owner);
return RadioInterface::OutOfRange;
}
Debug(m_owner,DebugConf,"Invalid %s=%g defaults to 1 [%p]",
param,newBalance,m_owner);
newBalance = 1;
}
if (m_txPowerBalance != newBalance) {
dbg = (m_state.m_tx.showPowerBalanceChange == 0);
if (dbg)
Debug(m_owner,DebugInfo,"TX power balance changed %g -> %g [%p]",
m_txPowerBalance,newBalance,m_owner);
m_txPowerBalance = newBalance;
}
}
float oldI = m_txPowerScaleI;
float oldQ = m_txPowerScaleQ;
// Update TX power scale
m_txPowerScaleI = m_txPowerScaleQ = 1;
if (m_txPowerBalance > 1)
m_txPowerScaleQ /= m_txPowerBalance;
else if (m_txPowerBalance < 1)
m_txPowerScaleI *= m_txPowerBalance;
if (oldI == m_txPowerScaleI && oldQ == m_txPowerScaleQ)
return 0;
if (dbg)
Debug(m_owner,DebugInfo,"TX power scale changed I: %g -> %g Q: %g -> %g [%p]",
oldI,m_txPowerScaleI,oldQ,m_txPowerScaleQ,m_owner);
m_txPowerBalanceChanged = true;
return 0;
}
unsigned int BrfLibUsbDevice::internalSetFreqOffs(float val, float* newVal, String* error)
{
val = clampFloat(val,BRF_FREQ_OFFS_MIN,BRF_FREQ_OFFS_MAX,"FrequencyOffset");
String e;
// val has an 8 bit integer range with float precision, which need to be converted to 16 bit integer
uint32_t voltageDAC = val * 256;
unsigned int status = gpioWrite(0x22,voltageDAC,2,&e);
if (status)
return showError(status,e,"FrequencyOffset set failed",error);
if (m_freqOffset != val) {
Debug(m_owner,(m_freqOffset != val) ? DebugInfo : DebugAll,
"FrequencyOffset changed %g -> %g [%p]",m_freqOffset,val,m_owner);
m_freqOffset = val;
}
else
Debug(m_owner,DebugAll,"FrequencyOffset set to %g [%p]",val,m_owner);
if (newVal)
*newVal = val;
return 0;
}
unsigned int BrfLibUsbDevice::internalSetFrequency(bool tx, uint64_t val, String* error)
{
XDebug(m_owner,DebugAll,"BrfLibUsbDevice::setFrequency("FMT64U",%s) [%p]",
val,brfDir(tx),m_owner);
String e;
unsigned int status = 0;
BrfDevTmpAltSet tmpAltSet(this,status,&e,"frequency set");
if (val < BRF_FREQUENCY_MIN || val > BRF_FREQUENCY_MAX) {
status = RadioInterface::OutOfRange;
e = "Value out of range";
}
uint32_t hz = (uint32_t)val;
while (!status) {
uint8_t addr = lmsFreqAddr(tx);
uint8_t pllFreq = 0xff;
for (int i = 0; s_freqLimits[i]; i += 3)
if (hz >= s_freqLimits[i] && hz <= s_freqLimits[i + 1]) {
pllFreq = s_freqLimits[i + 2];
break;
}
if (pllFreq == 0xff) {
status = setUnkValue(e,"frequency " + String(hz));
break;
}
// Integer part
uint64_t vco_x = ((uint64_t)1) << ((pllFreq & 7) - 3);
uint64_t tmp = (vco_x * hz) / s_freqRefClock;
if (tmp > 0xffff) {
e.printf("The integer part " FMT64U " of the frequency is too big",tmp);
status = RadioInterface::Failure;
break;
}
uint16_t nint = (uint16_t)tmp;
// Fractional part
tmp = (1 << 23) * (vco_x * hz - nint * s_freqRefClock);
tmp = (tmp + s_freqRefClock / 2) / s_freqRefClock;
if (tmp > 0xffffffff) {
e.printf("The fractional part " FMT64U " of the frequency is too big",tmp);
status = RadioInterface::Failure;
break;
}
uint32_t nfrac = (uint32_t)tmp;
bool lowBand = brfIsLowBand(hz);
// Reset CLK_EN for Rx/Tx DSM SPI
BRF_FUNC_CALL_BREAK(lmsSet(0x09,0x05,&e));
// Set PLL frequency and output buffer selection
pllFreq <<= 2;
pllFreq |= (lowBand ? 0x01 : 0x02);
BRF_FUNC_CALL_BREAK(lmsWrite(addr + 5,pllFreq,&e));
// Set frequency NINT/NFRAC
uint8_t d[4] = {(uint8_t)(nint >> 1),
(uint8_t)(((nint & 1) << 7) | ((nfrac >> 16) & 0x7f)),
(uint8_t)(nfrac >> 8),(uint8_t)nfrac};
BRF_FUNC_CALL_BREAK(accessPeripheral(UartDevLMS,true,addr,d,&e,4));
// Set PLL currents (ICHP=1.2mA, OFFUP=30uA, OFFDOWN=0mA)
BRF_FUNC_CALL_BREAK(lmsSet(addr + 6,0x0c,0x1f,&e));
BRF_FUNC_CALL_BREAK(lmsSet(addr + 7,0x03,0x1f,&e));
BRF_FUNC_CALL_BREAK(lmsSet(addr + 8,0x00,0x1f,&e));
// Loop through the VCOCAP to figure out optimal values
BRF_FUNC_CALL_BREAK(tuneVcocap(addr,&e));
// Reset CLK_EN for Rx/Tx DSM SPI
BRF_FUNC_CALL_BREAK(lmsReset(0x09,0x05,&e));
// Select PA/LNA amplifier (don't do it if loopback is enabled)
if (m_state.m_loopback == LoopNone)
BRF_FUNC_CALL_BREAK(selectPaLna(tx,lowBand,&e));
// Set GPIO band according to the frequency
uint32_t gpio = 0;
BRF_FUNC_CALL_BREAK(gpioRead(0,gpio,4,&e));
uint32_t band = lowBand ? 2 : 1;
int shift = tx ? 3 : 5;
gpio &= (uint32_t)~(3 << shift);
gpio |= (uint32_t)(band << shift);
BRF_FUNC_CALL_BREAK(gpioWrite(0,gpio,4,&e));
BRF_FUNC_CALL_BREAK(internalSetFreqOffs(m_freqOffset,0,&e));
break;
}
if (status) {
e.printf(1024,"Failed to set %s frequency to "FMT64U"Hz - %s",brfDir(tx),val,e.c_str());
return showError(status,e,0,error);
}
if (getDirState(tx).frequency != hz) {
getDirState(tx).frequency = hz;
Debug(m_owner,DebugInfo,"%s frequency set to %gMHz offset=%g [%p]",
brfDir(tx),(double)hz / 1000000,m_freqOffset,m_owner);
}
else
Debug(m_owner,DebugAll,"%s frequency set to %gMHz offset=%g [%p]",
brfDir(tx),(double)hz / 1000000,m_freqOffset,m_owner);
return 0;
}
unsigned int BrfLibUsbDevice::internalGetFrequency(bool tx, uint32_t* hz, String* error)
{
String e;
unsigned int status = 0;
uint32_t freq = 0;
BrfDevTmpAltSet tmpAltSet(this,status,&e,tx ? "TX frequency get" : "RX frequency get");
while (!status) {
uint8_t addr = lmsFreqAddr(tx);
uint8_t data = 0;
uint64_t fint = 0; // Integer part of the freq
// Reading the integer part of the frequency
BRF_FUNC_CALL_BREAK(lmsRead(addr + 0,data,&e));
fint = (uint64_t)data << 24;
BRF_FUNC_CALL_BREAK(lmsRead(addr + 1,data,&e));
fint |= (data & 0x80) << 16;
// Read the fractionary part of the frequency
fint |= ((uint64_t)data & 0x7f) << 16;
BRF_FUNC_CALL_BREAK(lmsRead(addr + 2,data,&e));
fint |= (uint64_t)data << 8;
BRF_FUNC_CALL_BREAK(lmsRead(addr + 3,data,&e));
fint |= data;
// read the freq division
BRF_FUNC_CALL_BREAK(lmsRead(addr + 5,data,&e));
uint32_t division = data >> 2; // VCO division ratio
division = 1 << ((division & 7) + 20);
freq = (uint32_t)(((s_freqRefClock * fint) + (division >> 1)) / division);
break;
}
if (status == 0) {
getDirState(tx).frequency = freq;
if (hz)
*hz = freq;
XDebug(m_owner,DebugAll,"Got %s frequency %uHz [%p]",brfDir(tx),freq,m_owner);
return 0;
}
e.printf(1024,"Failed to retrieve %s frequency - %s",brfDir(tx),e.c_str());
return showError(status,e,0,error);
}
// Retrieve TX/RX timestamp
unsigned int BrfLibUsbDevice::internalGetTimestamp(bool tx, uint64_t& ts, String* error)
{
String e;
unsigned int status = 0;
while (true) {
uint32_t low = 0;
uint32_t high = 0;
uint8_t addr = tx ? 0x18 : 0x10;
BRF_FUNC_CALL_BREAK(gpioRead(addr,low,4,&e));
BRF_FUNC_CALL_BREAK(gpioRead(addr + 4,high,4,&e));
ts = ((uint64_t)high << 31) | (low >> 1);
XDebug(m_owner,DebugAll,"Got %s ts=" FMT64U " [%p]",brfDir(tx),ts,m_owner);
return 0;
}
e.printf(1024,"Failed to retrieve %s timestamp - %s",brfDir(tx),e.c_str());
return showError(status,e,0,error);
}
// USB peripheral operation method
unsigned int BrfLibUsbDevice::lusbSetAltInterface(int val, String* error)
{
if (m_altSetting == val)
return 0;
unsigned int status = 0;
String e;
if (val >= BRF_ALTSET_MIN && val <= BRF_ALTSET_MAX)
status = lusbCheckSuccess(
::libusb_set_interface_alt_setting(m_devHandle,0,val),&e);
else
status = setUnkValue(e);
if (status == 0) {
DDebug(m_owner,DebugAll,"Alt interface changed %s -> %s [%p]",
altSetName(m_altSetting),altSetName(val),m_owner);
m_altSetting = val;
return 0;
}
String prefix;
prefix << "Failed to change alt interface to ";
if (val >= BRF_ALTSET_MIN && val <= BRF_ALTSET_MAX)
prefix << altSetName(val);
else
prefix << val;
return showError(status,e,prefix,error);
}
// Wrapper for libusb_control_transfer
unsigned int BrfLibUsbDevice::lusbCtrlTransfer(uint8_t reqType, int8_t request,
uint16_t value, uint16_t index, uint8_t* data, uint16_t len, String* error,
unsigned int tout)
{
#ifdef DEBUGGER_DEVICE_METH
String tmp;
//tmp.hexify(data,len,' ');
//tmp = " data=" + tmp;
Debugger d(DebugAll,"BrfLibUsbDevice::lusbCtrlTransfer()",
" (0x%x,0x%x,0x%x,%u,%p,%u,%u)%s [%p]",
reqType,request,value,index,data,len,tout,tmp.safe(),m_owner);
#endif
int code = ::libusb_control_transfer(m_devHandle,reqType,request,value,
index,data,len,tout ? tout : m_ctrlTout);
if (code == (int)len)
return 0;
String e;
unsigned int status = (code < 0) ? lusbCheckSuccess(code,&e) :
RadioInterface::Failure;
return showError(status,e,"Incomplete USB CTRL transfer",error);
}
// Wrapper for libusb bulk transfer
unsigned int BrfLibUsbDevice::lusbBulkTransfer(uint8_t endpoint, uint8_t* data,
unsigned int len, unsigned int* transferred, String* error, unsigned int tout)
{
#ifdef DEBUGGER_DEVICE_METH
Debugger d(DebugAll,"BrfLibUsbDevice::lusbBulkTransfer()",
" (0x%x,%p,%u,%u) [%p]",endpoint,data,len,tout,m_owner);
#endif
int nIO = 0;
int code = ::libusb_bulk_transfer(m_devHandle,endpoint,
data,len,&nIO,tout ? tout : m_bulkTout);
if (transferred)
*transferred = nIO;
if ((int)len == nIO)
return 0;
String e;
unsigned int status = (code < 0) ? lusbCheckSuccess(code,&e) :
RadioInterface::Failure;
return showError(status,e,"Incomplete USB BULK transfer",error);
}
// Make an async usb transfer
unsigned int BrfLibUsbDevice::syncTransfer(int ep, uint8_t* data, unsigned int len,
String* error)
{
LusbTransfer& t = m_usbTransfer[ep];
#ifdef DEBUG
if ((ep == EpReadSamples || ep == EpSendSamples) &&
!getIO(ep == EpSendSamples).mutex.locked())
Debug(m_owner,DebugFail,"syncTransfer() %s not locked [%p]",
brfDir(ep == EpSendSamples),m_owner);
if (t.running())
Debug(m_owner,DebugFail,"EP %s transfer is running [%p]",
lookup(ep,s_usbEndpoint),m_owner);
#endif
#ifdef DEBUGGER_DEVICE_METH
String tmp;
//tmp.hexify(data,len,' ');
//tmp = " data=" + tmp;
Debugger d(DebugAll,"BrfLibUsbDevice::syncTransfer()",
" (%s,%p,%u) transfer=(%p)%s [%p]",
lookup(ep,s_usbEndpoint),data,len,&t,tmp.safe(),m_owner);
#else
XDebug(m_owner,DebugAll,"syncTransfer ep=%s data=(%p) len=%u [%p]",
lookup(ep,s_usbEndpoint),data,len,m_owner);
#endif
t.running(true);
unsigned int cStatus = 0;
bool checkCancelled = !m_closingDevice;
if (t.fillBulk(data,len,m_syncTout) && t.submit()) {
while (t.running()) {
struct timeval tv;
tv.tv_usec = 3 * Thread::idleUsec();
tv.tv_sec = 0;
::libusb_handle_events_timeout_completed(m_context,&tv,0);
if (checkCancelled && t.running() && cStatus == 0 &&
(cStatus = cancelled()) != 0) {
Debug(m_owner,DebugInfo,"Cancelling transfer %s [%p]",
lookup(ep,s_usbEndpoint),m_owner);
t.cancel();
}
}
}
Lock lck(t);
t.running(false);
if (checkCancelled && !t.status)
t.status = cancelled(&t.error);
return showError(t.status,t.error,"SYNC transfer failed",error);
}
// Read the value of a specific GPIO register
unsigned int BrfLibUsbDevice::gpioRead(uint8_t addr, uint32_t& value, uint8_t len,
String* error, const char* loc)
{
len = clampInt(len,1,sizeof(value),"GPIO read items",DebugCrit);
uint8_t t[sizeof(value)];
unsigned int status = accessPeripheral(UartDevGPIO,false,addr,t,error,len,loc);
if (status)
return status;
value = 0;
// Data is in little endian order
#ifdef LITTLE_ENDIAN
for (uint8_t i = 0; i < len; i++)
value |= (uint64_t)(t[i] << (i * 8));
#else
for (uint8_t i = 0; i < len; i++)
value |= (uint64_t)(t[i] << ((len - i - 1) * 8));
#endif
return 0;
}
// Write a value to a specific GPIO register
unsigned int BrfLibUsbDevice::gpioWrite(uint8_t addr, uint32_t value, uint8_t len,
String* error, const char* loc)
{
if (addr == 0) {
if (m_devSpeed == LIBUSB_SPEED_SUPER)
value &= ~BRF_GPIO_SMALL_DMA_XFER;
else if (m_devSpeed == LIBUSB_SPEED_HIGH)
value |= BRF_GPIO_SMALL_DMA_XFER;
else
Debug(m_owner,DebugStub,"GPIO write: unhandled speed [%p]",m_owner);
}
len = clampInt(len,1,sizeof(value),"GPIO write items",DebugCrit);
uint8_t t[sizeof(value)];
// Data is in little endian order
#ifdef LITTLE_ENDIAN
for (uint8_t i = 0; i < len; i++)
t[i] = (uint8_t)(value >> (i * 8));
#else
for (uint8_t i = 0; i < len; i++)
t[i] = (uint8_t)(value >> ((len - i - 1) * 8));
#endif
return accessPeripheral(UartDevGPIO,true,addr,t,error,len,loc);
}
unsigned int BrfLibUsbDevice::lmsWrite(const String& str, bool updStat, String* error)
{
if (!str)
return 0;
String e;
unsigned int status = 0;
while (true) {
DataBlock db;
if (!db.unHexify(str)) {
status = setErrorFail(&e,"Invalid hex string");
break;
}
if ((db.length() % 2) != 0) {
status = setErrorFail(&e,"Invalid string length");
break;
}
Debug(m_owner,DebugAll,"Writing '%s' to LMS [%p]",str.c_str(),m_owner);
uint8_t* b = db.data(0);
for (unsigned int i = 0; !status && i < db.length(); i += 2) {
b[i] &= ~0x80;
status = lmsWrite(b[i],b[i + 1],&e);
}
if (!status && updStat)
status = updateStatus(&e);
if (!status)
return 0;
break;
}
e.printf(1024,"LMS write '%s' failed - %s",str.c_str(),e.c_str());
return showError(status,e,0,error);
}
// Read the lms configuration
unsigned int BrfLibUsbDevice::lmsRead(String& dest, const String* read,
bool readIsInterleaved, String* error)
{
String e;
unsigned int status = 0;
while (true) {
DataBlock db;
if (read) {
DataBlock tmp;
if (!tmp.unHexify(*read)) {
status = setErrorFail(&e,"Invalid hex string");
break;
}
if (readIsInterleaved) {
if ((tmp.length() % 2) != 0) {
status = setErrorFail(&e,"Invalid string length");
break;
}
db = tmp;
}
else {
db.resize(tmp.length() * 2);
for (unsigned int i = 0; i < tmp.length(); i++)
*db.data(i * 2) = tmp[i];
}
}
else {
db.resize(127 * 2);
for (uint8_t i = 0; i < 127; i++)
*db.data(i * 2) = i;
}
Debug(m_owner,DebugAll,"Reading LMS [%p]",m_owner);
uint8_t* b = db.data(0);
for (unsigned int i = 0; !status && i < db.length(); i += 2) {
b[i] &= ~0x80;
status = lmsRead(b[i],b[i + 1],&e);
}
if (status)
break;
dest.hexify(db.data(),db.length());
return 0;
}
e.printf(1024,"LMS read '%s' failed - %s",TelEngine::c_safe(read),e.c_str());
return showError(status,e,0,error);
}
// Check LMS registers
unsigned int BrfLibUsbDevice::lmsCheck(const String& what, String* error)
{
if (!what)
return 0;
String e;
unsigned int status = 0;
while (true) {
DataBlock db;
bool haveMask = (what[0] == '+');
unsigned int delta = haveMask ? 1 : 0;
if (!db.unHexify(what.c_str() + delta,what.length() - delta)) {
status = setErrorFail(&e,"Invalid hex string");
break;
}
unsigned int div = haveMask ? 3 : 2;
if ((db.length() % div) != 0) {
status = setErrorFail(&e,"Invalid string length");
break;
}
unsigned int n = db.length() / div;
uint8_t b[4];
uint8_t* d = db.data(0);
String diff;
String s;
for (unsigned int i = 0; i < n; i++) {
b[0] = *d++ & ~0x80;
b[1] = *d++;
b[2] = 0;
b[3] = (div > 2) ? *d++ : 0xff;
BRF_FUNC_CALL_BREAK(lmsRead(b[0],b[2],&e));
if ((b[1] & b[3]) != (b[2] & b[3]))
diff.append(s.hexify(b,div + 1)," ");
}
if (status)
break;
if (error)
*error = diff;
else if (diff)
Debug(m_owner,DebugNote,"Check LMS '%s' diff: %s [%p]",
what.c_str(),diff.c_str(),m_owner);
return 0;
}
e.printf(1024,"LMS check '%s' - %s",what.c_str(),e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::lnaSelect(int lna, String* error)
{
String e;
unsigned int status = 0;
bool valid = (lna >= 0 && lna <= 3);
while (valid) {
uint8_t data = 0;
BRF_FUNC_CALL_BREAK(lmsRead(0x75,data,&e));
BRF_FUNC_CALL_BREAK(lmsWrite(0x75,(data & ~0x30) | (lna << 4),&e));
int old = (data >> 4) & 0x03;
int level = old != lna ? DebugInfo : DebugAll;
if (lna != LmsLnaNone)
Debug(m_owner,level,"LNA %d selected [%p]",lna,m_owner);
else
Debug(m_owner,level,"LNAs disabled [%p]",m_owner);
return 0;
}
if (!valid)
status = setUnkValue(e);
if (lna != LmsLnaNone)
e.printf(1024,"Failed to select LNA %d - %s",lna,e.c_str());
else
e.printf(1024,"Failed to disable LNAs - %s",e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::lnaEnable(bool on, String* error)
{
String e;
unsigned int status = 0;
while (true) {
uint8_t data = 0;
BRF_FUNC_CALL_BREAK(lmsRead(0x7d,data,&e));
BRF_FUNC_CALL_BREAK(lmsWrite(0x7d,on ? (data & ~0x01) : (data | 0x01),&e));
Debug(m_owner,on == ((data & 0x01) == 0) ? DebugAll : DebugInfo,
"%s LNA RXFE [%p]",enabledStr(on),m_owner);
return 0;
}
e.printf("Failed to %s LNA RXFE - %s",enableStr(on),e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::lnaGainSet(uint8_t value, String* error)
{
const char* what = lookup(value,s_lnaGain);
XDebug(m_owner,DebugAll,"lnaGainSet(%u,'%s') [%p]",value,what,m_owner);
String e;
unsigned int status = 0;
if (!what || value == LnaGainUnhandled)
status = setUnkValue(e);
while (status == 0) {
uint8_t data = 0;
BRF_FUNC_CALL_BREAK(lmsRead(0x75,data,&e));
uint8_t old = (uint8_t)((data >> 6) & 0x03);
data &= ~(3 << 6);
data |= ((value & 3) << 6);
BRF_FUNC_CALL_BREAK(lmsWrite(0x75,data,&e));
if (old != value)
Debug(m_owner,DebugInfo,"LNA GAIN set to %s [%p]",what,m_owner);
return 0;
}
e.printf(1024,"Failed to set LNA GAIN %u (%s) - %s",value,what,e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::lnaGainGet(uint8_t& value, String* error)
{
uint8_t data = 0;
unsigned int status = lmsRead(0x75,data,error,"LNA gain read register");
if (status)
return status;
data >>= 6;
data &= 3;
value = data;
if (value != LnaGainUnhandled)
return 0;
String e;
e.printf("LNA gain read abnormal value 0x%x",data);
return showError(RadioInterface::OutOfRange,e,0,error);
}
unsigned int BrfLibUsbDevice::internalSetLpfBandwidth(bool tx, uint32_t band,
String* error)
{
String e;
unsigned int status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e);
while (!status) {
uint8_t data = 0;
uint8_t reg = lmsLpfAddr(tx);
BRF_FUNC_CALL_BREAK(lmsRead(reg,data,&e));
uint8_t i = bw2index(band);
uint8_t bw = 15 - i;
data &= ~0x3c; // Clear out previous bandwidth setting
data |= (bw << 2); // Apply new bandwidth setting
BRF_FUNC_CALL_BREAK(lmsWrite(reg,data,&e));
bool changed = (getDirState(tx).lpfBw != s_bandSet[i]);
getDirState(tx).lpfBw = s_bandSet[i];
Debug(m_owner,changed ? DebugInfo : DebugAll,
"%s LPF bandwidth set to %u (from %u, reg=0x%x) [%p]",
brfDir(tx),getDirState(tx).lpfBw,band,data,m_owner);
return 0;
}
e.printf(1024,"Failed to set %s LPF bandwidth %u: %s",brfDir(tx),band,e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::internalSetLpf(bool tx, int lpf, String* error)
{
const char* what = lookup(lpf,s_lpf);
XDebug(m_owner,DebugAll,"internalSetLpf(%u,%d,'%s') [%p]",tx,lpf,what,m_owner);
uint8_t addr = lmsLpfAddr(tx);
uint8_t reg1 = 0;
uint8_t reg2 = 0;
String e;
unsigned int status = 0;
if (what)
status = lmsRead2(addr,reg1,addr + 1,reg2,&e);
else
status = setUnkValue(e,0,"value");
if (status == 0) {
// Clear EN_LPF
switch (lpf) {
case LpfDisabled:
reg1 &= 0xfd; // Disable LPF: reset EN_LPF
reg2 &= 0xbf; // Normal operation: reset BYP_EN_LPF
break;
case LpfBypass:
reg1 &= 0xfd; // Disable LPF: reset EN_LPF
reg1 |= 0x40; // Bypass LPF: set BYP_EN_LPF
break;
case LpfNormal:
reg1 |= 0x02; // Enable LPF: set EN_LPF
reg2 &= 0xbf; // Normal operation: reset BYP_EN_LPF
break;
default:
status = setUnkValue(e,0,"value");
}
if (status == 0) {
status = lmsWrite2(addr,reg1,addr + 1,reg2,&e);
if (status == 0) {
if (getDirState(tx).lpf != lpf) {
getDirState(tx).lpf = lpf;
Debug(m_owner,DebugInfo,"%s LPF set to '%s' [%p]",
brfDir(tx),what,m_owner);
}
return 0;
}
}
}
e.printf(1024,"Failed to set %s LPF %u (%s) - %s",
brfDir(tx),lpf,TelEngine::c_safe(what),e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::internalGetLpf(bool tx, int* lpf, String* error)
{
uint8_t addr = lmsLpfAddr(tx);
uint8_t reg1 = 0;
uint8_t reg2 = 0;
String e;
unsigned int status = lmsRead2(addr,reg1,addr + 1,reg2,&e);
if (status == 0) {
int l = decodeLpf(reg1,reg2);
if (l != LpfInvalid) {
getDirState(tx).lpf = l;
if (lpf)
*lpf = l;
XDebug(m_owner,DebugAll,"Got %s LPF %d (%s) [%p]",
brfDir(tx),l,lookup(l,s_lpf),m_owner);
return 0;
}
status = RadioInterface::OutOfRange;
e = "Invalid values, enabled and bypassed";
}
e.printf(1024,"Failed to retrieve %s LPF - %s",brfDir(tx),e.c_str());
return showError(status,e,0,error);
}
// Fill the m_list member of the class
unsigned int BrfLibUsbDevice::updateDeviceList(String* error)
{
clearDeviceList();
int n = ::libusb_get_device_list(m_context,&m_list);
if (n >= 0) {
m_listCount = n;
return 0;
}
String e;
unsigned int status = lusbCheckSuccess(n,&e);
return showError(status,e,"Failed to enumerate USB devices",error);
}
// Enable/disable RF and sample circulation on both RX and TX sides
unsigned int BrfLibUsbDevice::enableRf(bool tx, bool on, bool frontEndOnly, String* error)
{
#ifdef DEBUGGER_DEVICE_METH
Debugger d(DebugAll,"BrfLibUsbDevice::enableRf()",
" tx=%s on=%s frontEndOnly=%s [%p]",String::boolText(tx),String::boolText(on),
String::boolText(frontEndOnly),m_owner);
#endif
BrfDevDirState& dirState = getDirState(tx);
unsigned int status = 0;
String e;
resetTimestamps(tx);
if (!m_devHandle) {
if (!on) {
dirState.rfEnabled = false;
return 0;
}
status = RadioInterface::NotInitialized;
e = "Not open";
}
if (!status) {
// RF front end
uint8_t addr = tx ? 0x40 : 0x70;
uint8_t val = tx ? 0x02 : 0x01;
status = lmsChangeMask(addr,val,on,&e);
if (!status && !frontEndOnly)
status = enableRfFpga(tx,on,&e);
}
bool ok = on && (status == 0);
if (dirState.rfEnabled == ok) {
dirState.rfEnabled = ok;
return status;
}
dirState.rfEnabled = ok;
const char* fEnd = frontEndOnly ? " front end" : "";
if (status == 0) {
Debug(m_owner,DebugAll,"%s RF %s%s [%p]",
enabledStr(on),brfDir(tx),fEnd,m_owner);
return 0;
}
e.printf(1024,"Failed to %s RF %s%s - %s",enableStr(on),brfDir(tx),fEnd,e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::enableRfFpga(bool tx, bool on, String* error)
{
uint8_t request = tx ? BRF_USB_CMD_RF_TX : BRF_USB_CMD_RF_RX;
uint32_t buf = (uint32_t)-1;
uint16_t value = on ? 1 : 0;
String e;
unsigned int status = lusbCtrlTransfer(LUSB_CTRLTRANS_IFACE_VENDOR_IN,
request,value,0,(uint8_t*)&buf,sizeof(buf),&e);
if (status == 0 && le32toh(buf))
status = setErrorFail(&e,"Device failure");
if (!status)
return 0;
e.printf(1024,"FPGA RF %s failed - %s",enableStr(on),e.c_str());
return showError(status,e,0,error);
}
// Check if fpga is loaded
unsigned int BrfLibUsbDevice::checkFpga()
{
String error;
int32_t data = 0;
unsigned int status = vendorCommand(BRF_USB_CMD_QUERY_FPGA_STATUS,BRF_ENDP_RX_SAMPLES,
data,&error);
if (status == 0) {
if (le32toh(data)) {
Debug(m_owner,DebugAll,"The FPGA is already configured [%p]",m_owner);
return 0;
}
Debug(m_owner,DebugAll,"The FPGA is not configured [%p]",m_owner);
return RadioInterface::NotInitialized;
}
Debug(m_owner,DebugNote,"FPGA check failed: %s [%p]",error.c_str(),m_owner);
return status;
}
// Restore device after loading the FPGA
unsigned int BrfLibUsbDevice::restoreAfterFpgaLoad(String* error)
{
String e;
unsigned int status = 0;
BrfDevTmpAltSet tmpAltSet(this,status,&e,"Restore after FPGA load");
while (!status) {
uint32_t gpio = 0;
status = gpioRead(0,gpio,4,&e);
if (status)
break;
if (gpio & 0x7fff) {
e.printf("Unexpected FPGA state 0x%x",gpio);
status = RadioInterface::Failure;
break;
}
// Enable the LMS and select the low band
status = gpioWrite(0,0x57,4,&e,"Failed to enable LMS and/or low band");
if (status)
break;
// Disable the TX/RX
if ((status = enableRfBoth(false,true,&e)) != 0)
break;
// Enabling LMS on TX side
status = lmsWrite(0x05,0x3e,&e,"Failed to enable LMS TX");
if (status)
break;
break;
}
if (status == 0) {
XDebug(m_owner,DebugAll,"Restored device after FPGA load [%p]",m_owner);
return 0;
}
return showError(status,e,"Failed to restore device after FPGA load",error);
}
// Change some LMS registers default value on open
unsigned int BrfLibUsbDevice::openChangeLms(const NamedList& params, String* error)
{
// See Lime FAQ document Section 5.27
static const String s_def = "4740592964367937";
String e;
unsigned int status = 0;
BrfDevTmpAltSet tmpAltSet(this,status,&e,"Open change LMS");
if (!status) {
const String* s = params.getParam(YSTRING("open_write_lms"));
if (s && *s != s_def)
Debug(m_owner,DebugNote,"Open: writing LMS '%s' [%p]",s->c_str(),m_owner);
else
s = &s_def;
status = lmsWrite(*s,false,&e);
}
if (status == 0) {
XDebug(m_owner,DebugAll,"Changed default LMS values [%p]",m_owner);
return 0;
}
return showError(status,e,"Failed to change LMS defaults",error);
}
// Reset the Usb interface using an ioctl call
unsigned int BrfLibUsbDevice::resetUsb(String* error)
{
String e;
unsigned int status = openDevice(false,&e);
if (status)
return showError(status,e,"USB reset failed",error);
status = lusbCheckSuccess(::libusb_reset_device(m_devHandle),&e,"USB reset failed ");
if (!status)
Debug(m_owner,DebugAll,"Reset USB device bus=%d addr=%d [%p]",
m_devBus,m_devAddr,m_owner);
closeDevice();
return showError(status,e,0,error);
}
// Set the VCTCXO configuration to the correct value
unsigned int BrfLibUsbDevice::tuneVcocap(uint8_t addr, String* error)
{
uint8_t data = 0;
unsigned int status = lmsRead(addr + 9,data,error,"VCTCXO tune");
if (status)
return status;
uint8_t vcocap = 32;
uint8_t vtune = 0;
uint8_t step = vcocap >> 1;
data &= ~(0x3f);
for (int i = 0; i < 6; i++) {
if ((status = lmsWrite(addr + 9,vcocap | data,error,"VCTCXO tune")) != 0)
return status;
if ((status = lmsRead(addr + 10,vtune,error,"VCTCXO tune")) != 0)
return status;
vtune >>= 6;
if (vtune == VCO_NORM) {
XDebug(m_owner,DebugInfo,"tuneVcocap: Found normal VCO [%p]",m_owner);
break;
}
if (vtune == VCO_HIGH) {
XDebug(m_owner,DebugInfo,"tuneVcocap: VCO high [%p]",m_owner);
vcocap += step;
}
else if (vtune == VCO_LOW) {
XDebug(m_owner,DebugInfo,"tuneVcocap: VCO low [%p]",m_owner);
vcocap -= step ;
}
else
return setError(RadioInterface::Failure,error,
"VCTCXO tune - invalid tunning");
step >>= 1;
}
if (vtune != VCO_NORM)
return setError(RadioInterface::Failure,error,"VCTCXO tune - tunning not locked");
uint8_t start = vcocap;
while (start > 0 && vtune == VCO_NORM) {
start--;
if ((status = lmsWrite(addr + 9,start | data,error,"VCTCXO tune")) != 0)
return status;
if ((status = lmsRead(addr + 10,vtune,error,"VCTCXO tune")) != 0)
return status;
vtune >>= 6;
}
start++;
XDebug(m_owner,DebugInfo,"tuneVcocap: Found lower limit %u [%p]",start,m_owner);
if ((status = lmsWrite(addr + 9,vcocap | data,error,"VCTCXO tune")) != 0)
return status;
if ((status = lmsRead(addr + 10,vtune,error,"VCTCXO tune")) != 0)
return status;
vtune >>= 6;
uint8_t stop = vcocap;
while (stop < 64 && vtune == VCO_NORM) {
stop++;
if ((status = lmsWrite(addr + 9,stop | data,error,"VCTCXO tune")) != 0)
return status;
if ((status = lmsRead(addr + 10,vtune,error,"VCTCXO tune")) != 0)
return status;
vtune >>= 6;
}
stop--;
XDebug(m_owner,DebugAll,"tuneVcocap: Found lower limit %u [%p]",stop,m_owner);
vcocap = (start + stop) >> 1;
XDebug(m_owner,DebugInfo,"tuneVcocap: VCOCAP=%u [%p]",vcocap,m_owner);
if ((status = lmsWrite(addr + 9,vcocap | data,error,"VCTCXO tune")) != 0)
return status;
if ((status = lmsRead(addr + 10,vtune,error,"VCTCXO tune")) != 0)
return status;
vtune >>= 6;
DDebug(m_owner,DebugInfo,"tuneVcocap: VCTCXO=%u [%p]",vtune,m_owner);
if (vtune == VCO_NORM)
return 0;
return setError(RadioInterface::Failure,error,"VCTCXO tune failed");
}
// Send requests to the bladeRF device regarding the FPGA image configuration.
unsigned int BrfLibUsbDevice::vendorCommand(uint8_t cmd, uint8_t ep, uint8_t* data,
uint16_t len, String* error)
{
#ifdef DEBUGGER_DEVICE_METH
Debugger d(DebugAll,"BrfLibUsbDevice::vendorCommand()",
" (0x%x,0x%x,%p,%u) [%p]",cmd,ep,data,len,m_owner);
#endif
String e;
unsigned int status = lusbCtrlTransfer(LUSB_CTRLTRANS_IFACE_VENDOR | ep,
cmd,0,0,data,len,&e);
if (!status)
return 0;
e.printf(1024,"Vendor command 0x%x endpoint=0x%x failed - %s",cmd,ep,e.c_str());
return showError(status,e,0,error);
}
// Access the bladeRF board in order to transmit data
unsigned int BrfLibUsbDevice::accessPeripheral(uint8_t dev, bool tx, uint8_t addr,
uint8_t* data, String* error, uint8_t len, const char* loc)
{
if (dev >= UartDevCount)
return RadioInterface::Failure;
const BrfPeripheral& uartDev = s_uartDev[dev];
#ifdef DEBUGGER_DEVICE_METH
String tmp;
if (tx)
tmp.hexify(data,len,' ');
Debugger debug(DebugInfo,"BrfLibUsbDevice::accessPeripheral()",
" dev=%s dir=%s addr=0x%x len=%u bits=%s [%p]",
uartDev.c_str(),brfDir(tx),addr,len,tmp.safe(),m_owner);
#endif
String e;
unsigned int status = 0;
uint8_t a = addr;
uint8_t* d = data;
uint8_t n = len;
uint8_t maskDirDev = (uint8_t)((tx ? 0x40 : 0x80) | uartDev.devId());
uint8_t buf[16] = {(uint8_t)'N'};
#define BRF_ACCESS_PERIPHERAL(nItems) \
{ \
buf[1] = (uint8_t)(maskDirDev | nItems); \
uint8_t* b = &buf[2]; \
::memset(b,0,sizeof(buf) - 2); \
for (uint8_t i = 0; i < nItems; i++) { \
*b++ = a + i; \
if (tx) \
*b = d[i]; \
b++; \
} \
status = syncTransfer(EpSendCtrl,buf,sizeof(buf),&e); \
if (!status) \
status = syncTransfer(EpReadCtrl,buf,sizeof(buf),&e); \
if (status == 0 && !tx) { \
b = &buf[3]; \
for (uint8_t i = 0; i < nItems; i++, b += 2) \
d[i] = *b; \
} \
}
if (n > 7) {
n = len % 7;
for (uint8_t full = len / 7; !status && full; full--, a += 7, d += 7)
BRF_ACCESS_PERIPHERAL(7);
}
if (n && !status)
BRF_ACCESS_PERIPHERAL(n);
#undef BRF_ACCESS_PERIPHERAL
if (status) {
e.printf(1024,"%s%s%s %s failed addr=0x%x len=%d - %s",
TelEngine::c_safe(loc),(loc ? " - " : ""),uartDev.c_str(),
tx ? "write" : "read",addr,len,e.c_str());
return showError(status,e,0,error);
}
if (!uartDev.trackDir(tx))
return 0;
String s;
if (!uartDev.haveTrackAddr()) {
if (m_owner->debugAt(DebugAll))
Debug(m_owner,DebugAll,"%s %s addr=0x%x len=%u '%s' [%p]",
uartDev.c_str(),brfDir(tx),addr,len,
s.hexify(data,len,' ').c_str(),m_owner);
}
else {
int level = uartDev.trackLevel();
bool levelOk = !level || m_owner->debugAt(level);
if (levelOk && (uartDev.isTrackRange(addr,len) >= 0)) {
unsigned int a = addr;
for (unsigned int i = 0; i < len && a < 256; i++, a++)
if (uartDev.isTrackAddr(a)) {
String tmp;
if (!s)
s << uartDev.c_str() << " " << brfDir(tx);
s.append(tmp.printf("(0x%x=0x%x)",(uint8_t)a,data[i])," ");
}
if (s) {
if (level)
Debug(m_owner,level,"%s [%p]",s.c_str(),m_owner);
else {
char b[50];
Debugger::formatTime(b);
Output("%s<%s> %s [%p]",b,m_owner->debugName(),s.c_str(),m_owner);
}
}
}
}
return 0;
}
unsigned int BrfLibUsbDevice::internalSetDcOffset(bool tx, bool i, int16_t value,
String* error)
{
int& old = i ? getDirState(tx).dcOffsetI : getDirState(tx).dcOffsetQ;
if (old == value)
return 0;
uint8_t addr = lmsCorrIQAddr(tx,i);
String e;
uint8_t data = 0;
unsigned int status = 0;
while (true) {
if (tx) {
if (value < BRF_TX_DC_OFFSET_MIN || value > BRF_TX_DC_OFFSET_MAX) {
status = setUnkValue(e,0,"value");
break;
}
}
else if (value < -BRF_RX_DC_OFFSET_MAX || value > BRF_RX_DC_OFFSET_MAX) {
status = setUnkValue(e,0,"value");
break;
}
BrfDevTmpAltSet tmpAltSet(this,status,&e,"DC offset set");
if (status)
break;
status = lmsRead(addr,data,&e);
if (status)
break;
if (tx)
// MSB bit is the sign (1: positive)
data = 128 + value;
else {
// MSB bit has nothing to do with RX DC offset
// Bit 6 is the sign bit (1: negative)
uint8_t b7 = (uint8_t)(data & 0x80);
if (value >= 0)
data = (uint8_t)((value >= 64) ? 0x3f : (value & 0x3f));
else {
data = (uint8_t)((value <= -64) ? 0x3f : ((-value) & 0x3f));
data |= 0x40;
}
data |= b7;
}
status = lmsWrite(addr,data,&e);
break;
}
if (status == 0) {
int tmp = decodeDCOffs(tx,data);
if (tmp != old) {
old = tmp;
if (getDirState(tx).showDcOffsChange == 0)
Debug(m_owner,DebugAll,"%s DC offset %c set to %d (from %d) reg=0x%x [%p]",
brfDir(tx),brfIQ(i),old,value,data,m_owner);
}
return 0;
}
e.printf(1024,"%s DC offset %c set to %d failed - %s",
brfDir(tx),brfIQ(i),value,e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::internalGetDcOffset(bool tx, bool i, int16_t* value,
String* error)
{
uint8_t addr = lmsCorrIQAddr(tx,i);
String e;
uint8_t data = 0;
unsigned int status = 0;
BrfDevTmpAltSet tmpAltSet(this,status,&e,"DC offset get");
if (!status)
status = lmsRead(addr,data,&e);
if (!status) {
int& old = i ? getDirState(tx).dcOffsetI : getDirState(tx).dcOffsetQ;
old = decodeDCOffs(tx,data);
if (value)
*value = old;
XDebug(m_owner,DebugAll,"Got %s DC offset %c %d (0x%x) [%p]",
brfDir(tx),brfIQ(i),old,data,m_owner);
return 0;
}
e.printf(1024,"%s DC offset %c get failed - %s",brfDir(tx),brfIQ(i),e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::enableTimestamps(bool on, String* error)
{
String e;
unsigned int status = 0;
while (true) {
uint32_t val = 0;
BRF_FUNC_CALL_BREAK(gpioRead(0,val,4,&e));
if (on)
val |= 0x10000;
else
val &= ~0x10000;
BRF_FUNC_CALL_BREAK(gpioWrite(0,val,4,&e));
if (on) {
BRF_FUNC_CALL_BREAK(gpioRead(0,val,4,&e));
if ((val & 0x10000) == 0) {
status = setError(RadioInterface::Failure,&e,"not enabled");
break;
}
resetTimestamps(true);
resetTimestamps(false);
setIoDontWarnTs(true);
setIoDontWarnTs(false);
}
Debug(m_owner,DebugAll,"%s timestamps [%p]",enabledStr(on),m_owner);
return 0;
}
e.printf(1024,"Failed to %s timestamps - %s",enableStr(on),e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::updateStatus(String* error)
{
unsigned int status = 0;
// Frequency (only if already set)
if (m_state.m_tx.frequency)
BRF_FUNC_CALL(internalGetFrequency(true));
if (m_state.m_rx.frequency)
BRF_FUNC_CALL(internalGetFrequency(false));
// Update VGA data
BRF_FUNC_CALL(internalGetTxVga(0,true,error));
BRF_FUNC_CALL(internalGetTxVga(0,false,error));
BRF_FUNC_CALL(internalGetRxVga(0,true,error));
BRF_FUNC_CALL(internalGetRxVga(0,false,error));
// LPF
internalGetLpf(true,0,error);
internalGetLpf(false,0,error);
// Update DC offsets
BRF_FUNC_CALL(internalGetDcOffset(true,true,0,error));
BRF_FUNC_CALL(internalGetDcOffset(true,false,0,error))
BRF_FUNC_CALL(internalGetDcOffset(false,true,0,error));
BRF_FUNC_CALL(internalGetDcOffset(false,false,0,error));
// Update FPGA correction
BRF_FUNC_CALL(internalGetFpgaCorr(true,CorrFpgaGain,0,error));
BRF_FUNC_CALL(internalGetFpgaCorr(true,CorrFpgaPhase,0,error));
BRF_FUNC_CALL(internalGetFpgaCorr(false,CorrFpgaGain,0,error));
BRF_FUNC_CALL(internalGetFpgaCorr(false,CorrFpgaPhase,0,error));
return status;
}
unsigned int BrfLibUsbDevice::paSelect(int pa, String* error)
{
String e;
unsigned int status = 0;
while (true) {
uint8_t data = 0;
BRF_FUNC_CALL_BREAK(lmsRead(0x44,data,&e));
// PA_EN bits 4-3: PA 1/2/none - bit 2: AUXPA (0: powered up, 1: powered down)
bool changed = false;
switch (pa) {
case LmsPaAux:
changed = (data & 0x04) != 0;
status = lmsWrite(0x44,data & ~0x04,&e);
break;
case LmsPa1:
changed = (data & 0x18) != 0x08;
status = lmsWrite(0x44,(data & ~0x18) | 0x08,&e);
break;
case LmsPa2:
changed = (data & 0x18) != 0x10;
status = lmsWrite(0x44,(data & ~0x18) | 0x10,&e);
break;
case LmsPaNone:
changed = (data & 0x18) != 0;
status = lmsWrite(0x44,data & ~0x18,&e);
break;
default:
Debug(m_owner,DebugFail,"Unhandled PA %d [%p]",pa,m_owner);
status = setUnkValue(e);
}
if (status)
break;
int level = changed ? DebugInfo : DebugAll;
if (pa != LmsPaNone)
Debug(m_owner,level,"%s enabled [%p]",lookup(pa,s_pa),m_owner);
else
Debug(m_owner,level,"PAs disabled [%p]",m_owner);
return 0;
}
if (pa != LmsPaNone)
e.printf(1024,"Failed to enable PA %s - %s",lookup(pa,s_pa),e.c_str());
else
e.printf(1024,"Failed to disable PAs - %s",e.c_str());
return showError(status,e,0,error);
}
int64_t BrfLibUsbDevice::clampInt(int64_t val, int64_t minVal, int64_t maxVal, const char* what,
int level)
{
if (val >= minVal && val <= maxVal)
return val;
int64_t c = val < minVal ? minVal : maxVal;
if (what)
Debug(m_owner,level,"Clamping %s " FMT64 " -> " FMT64 " [%p]",what,val,c,m_owner);
return c;
}
float BrfLibUsbDevice::clampFloat(float val, float minVal, float maxVal, const char* what,
int level)
{
if (val >= minVal && val <= maxVal)
return val;
float c = val < minVal ? minVal : maxVal;
if (what)
Debug(m_owner,level,"Clamping %s %g -> %g [%p]",what,val,c,m_owner);
return c;
}
unsigned int BrfLibUsbDevice::openDevice(bool claim, String* error)
{
closeDevice();
m_dev = 0;
unsigned int status = updateDeviceList(error);
if (status)
return status;
bool haveMatch = !m_serial.null();
bool foundMatched = false;
unsigned int failedDesc = 0;
ObjList found;
for (unsigned int i = 0; i < m_listCount; i++) {
libusb_device_descriptor desc;
if (::libusb_get_device_descriptor(m_list[i],&desc)) {
failedDesc++;
continue;
}
// OpenMoko 0x1d50 Product=0x6066
// Nuand 0x2cf0 Product=0x5246
if (!((desc.idVendor == 0x1d50 && desc.idProduct == 0x6066) ||
(desc.idVendor == 0x2cf0 && desc.idProduct == 0x5246)))
continue;
m_dev = m_list[i];
m_devBus = ::libusb_get_bus_number(m_dev);
m_devAddr = ::libusb_get_device_address(m_dev);
m_devSpeed = ::libusb_get_device_speed(m_dev);
DDebug(m_owner,DebugAll,"Opening device bus=%u addr=%u [%p]",bus(),addr(),m_owner);
String tmpError;
unsigned int tmpStatus = lusbCheckSuccess(::libusb_open(m_dev,&m_devHandle),
&tmpError,"Failed to open the libusb device ");
while (!tmpStatus) {
getDevStrDesc(m_devSerial,desc.iSerialNumber,"serial number");
if (haveMatch) {
if (m_serial != m_devSerial)
break;
foundMatched = true;
}
getDevStrDesc(m_devFwVerStr,4,"firmware version");
if (claim)
tmpStatus = lusbCheckSuccess(::libusb_claim_interface(m_devHandle,0),
&tmpError,"Failed to claim the interface ");
if (!tmpStatus) {
m_address.clear();
m_address << "USB/" << bus() << "/" << addr();
Debug(m_owner,DebugAll,"Opened device bus=%u addr=%u [%p]",bus(),addr(),m_owner);
return 0;
}
break;
}
String* tmp = new String(m_devBus);
*tmp << "/" << m_devAddr << "/" << m_devSerial;
found.append(tmp);
closeUsbDev();
m_dev = 0;
if (tmpStatus) {
status = tmpStatus;
if (error)
*error = tmpError;
}
if (foundMatched)
break;
}
String e;
if (haveMatch) {
e << "serial='" << m_serial << "' [";
if (!foundMatched)
e << "not ";
e << "found] ";
}
if (found.count()) {
e << "checked_devices=" << found.count();
String failed;
failed.append(found,",");
e << " (" << failed << ")";
}
else if (!haveMatch)
e << "no device found";
if (failedDesc)
e << " (failed_desc_retrieval=" << failedDesc << " device descriptor(s))";
if (status)
return setError(status,error,e);
if (found.skipNull() && (!haveMatch || foundMatched))
return setError(RadioInterface::NotInitialized,error,e);
return setError(RadioInterface::HardwareNotAvailable,error,e);
}
void BrfLibUsbDevice::closeDevice()
{
if (!m_devHandle)
return;
if (m_notifyOff) {
Engine::enqueue(buildNotify("stop"));
m_notifyOff = false;
}
//Debugger d(DebugNote,"closeDevice "," %s [%p]",m_owner->debugName(),m_owner);
m_closingDevice = true;
stopThreads();
internalPowerOn(false,false,false);
m_closingDevice = false;
closeUsbDev();
m_txIO.dataDumpFile.terminate(owner());
m_txIO.upDumpFile.terminate(owner());
m_rxIO.dataDumpFile.terminate(owner());
m_rxIO.upDumpFile.terminate(owner());
m_initialized = false;
Debug(m_owner,DebugAll,"Device closed [%p]",m_owner);
}
void BrfLibUsbDevice::closeUsbDev()
{
if (m_devHandle) {
::libusb_close(m_devHandle);
m_devHandle = 0;
}
m_devBus = -1;
m_devAddr = -1;
m_devSpeed = LIBUSB_SPEED_HIGH;
m_devSerial.clear();
m_devFwVerStr.clear();
m_devFpgaVerStr.clear();
m_devFpgaFile.clear();
m_devFpgaMD5.clear();
m_lmsVersion.clear();
}
void BrfLibUsbDevice::getDevStrDesc(String& data, uint8_t index, const char* what)
{
unsigned char buf[256];
int len = ::libusb_get_string_descriptor_ascii(m_devHandle,index,buf,sizeof(buf) - 1);
if (len >= 0) {
buf[len] = 0;
data = (const char*)buf;
return;
}
data.clear();
String tmp;
Debug(m_owner,DebugNote,"Failed to retrieve device %s %s [%p]",
what,appendLusbError(tmp,len).c_str(),m_owner);
}
// Read pages from device using control transfer
unsigned int BrfLibUsbDevice::ctrlTransferReadPage(uint8_t request, DataBlock& buf,
String* error)
{
if (!m_ctrlTransferPage)
return setError(RadioInterface::Failure,error,"Invalid CTRL transfer page size");
buf.resize(BRF_FLASH_PAGE_SIZE);
uint8_t* b = buf.data(0);
// Retrieve data from the firmware page buffer
for (unsigned int offs = 0; offs < buf.length(); offs += m_ctrlTransferPage) {
unsigned int status = lusbCtrlTransfer(LUSB_CTRLTRANS_DEV_VENDOR_IN,
request,0,offs,b + offs,m_ctrlTransferPage,error);
if (status)
return status;
}
return 0;
}
// Read calibration cache page from device
unsigned int BrfLibUsbDevice::readCalCache(String* error)
{
unsigned int status = 0;
BrfDevTmpAltSet tmpAltSet(this,BRF_ALTSET_SPI_FLASH,status,error,
"read calibration cache");
m_calCache.clear();
if (status == 0)
return ctrlTransferReadPage(BRF_USB_CMD_READ_CAL_CACHE,m_calCache,error);
return status;
}
static uint16_t crc16(uint8_t* buf, unsigned int len)
{
uint16_t crc = 0;
for (uint8_t* last = buf + len; buf < last; buf++) {
crc ^= (uint16_t)(((uint16_t)*buf) << 8);
for (int i = 0; i < 8; i++) {
if ((crc & 0x8000) != 0)
crc = (uint16_t)(crc << 1) ^ 0x1021;
else
crc = (uint16_t)(crc << 1);
}
}
return crc;
}
// Retrieve a filed from a buffer of elements
// Buffer format: 1 byte length + data + 2 bytes CRC16
const char* BrfLibUsbDevice::getBufField(String& value, const char* field)
{
if (TelEngine::null(field))
return "empty-field";
uint8_t* b = m_calCache.data(0);
unsigned int len = m_calCache.length();
if (!len)
return "calibration-cache-not-loaded";
for (uint8_t dataLen = 0; len; len -= dataLen, b += dataLen) {
dataLen = *b;
// No more data ?
if (dataLen == 0xff)
return "unexpected end of data";
// Do we have enough data ?
if (len < (dataLen + 2u))
return "wrong data - invalid field length";
uint16_t crc = le32toh(*(uint16_t*)(b + dataLen + 1));
uint16_t crcCheck = crc16(b,dataLen + 1);
if (crcCheck != crc)
return "wrong data - invalid CRC";
unsigned int fLen = 0;
const char* s = (const char*)(b + 1);
const char* f = field;
for (; fLen <= dataLen && *f && *s == *f; s++, f++)
fLen++;
if (!*f) {
value.assign(s,dataLen - fLen);
return 0;
}
dataLen += 3;
}
return "not found";
}
// Read calibration cache field
unsigned int BrfLibUsbDevice::getCalField(String& value, const String& name,
const char* desc, String* error)
{
// NOTE calibration cache may be obsolete, readCalCache is called at initialization only
String e = getBufField(value,name);
if (!e)
return 0;
e.printf(2048,"Failed to retrieve calibration cache field '%s' (%s) - %s",
name.c_str(),desc,e.c_str());
return showError(RadioInterface::Failure,e,0,error);
}
String& BrfLibUsbDevice::dumpCalCache(String& dest)
{
String e;
dest.append("(LEN|VALUE|CRC)"," ");
uint8_t* b = m_calCache.data(0);
unsigned int len = m_calCache.length();
for (uint8_t dataLen = 0; len; len -= dataLen, b += dataLen) {
dataLen = *b;
// No more data ?
if (dataLen == 0xff) {
len = 0;
break;
}
dest << " " << dataLen;
// Do we have enough data ?
if (len < (dataLen + 2u)) {
dest << "-|-";
break;
}
String crcS;
crcS.hexify(b + dataLen + 1,2);
uint16_t crc = le32toh(*(uint16_t*)(b + dataLen + 1));
uint16_t crcCheck = crc16(b,dataLen + 1);
if (crcCheck != crc)
crcS << "(invalid)";
dest << "|" << String((const char*)(b + 1),dataLen) << "|" << crcS;
dataLen += 3;
}
if (len)
dest << " garbage=" << len;
return dest;
}
// Update speed related data
unsigned int BrfLibUsbDevice::updateSpeed(const NamedList& params, String* error)
{
if (speed() == LIBUSB_SPEED_SUPER || speed() == LIBUSB_SPEED_HIGH) {
initBuffers(0,params.getIntValue("buffered_samples",2048),
params.getIntValue("tx_min_buffers"));
if (speed() == LIBUSB_SPEED_SUPER) {
m_radioCaps.rxLatency = clampIntParam(params,"rx_latency_super",4000,0,150000);
m_radioCaps.txLatency = clampIntParam(params,"tx_latency_super",10000,0,150000);
m_radioCaps.maxSampleRate = clampIntParam(params,"max_samplerate_super",
MAX_SAMPLERATE_SUPER,2 * BRF_SAMPLERATE_MIN,BRF_SAMPLERATE_MAX);
m_ctrlTransferPage = BRF_FLASH_PAGE_SIZE;
}
else {
m_radioCaps.rxLatency = clampIntParam(params,"rx_latency_high",7000,0,150000);
m_radioCaps.txLatency = clampIntParam(params,"tx_latency_high",20000,0,150000);
m_radioCaps.maxSampleRate = clampIntParam(params,"max_samplerate_high",
MAX_SAMPLERATE_HIGH,2 * BRF_SAMPLERATE_MIN,BRF_SAMPLERATE_MAX);
m_ctrlTransferPage = 64;
}
return 0;
}
m_minBufsSend = 1;
m_radioCaps.rxLatency = 0;
m_radioCaps.txLatency = 0;
m_radioCaps.maxSampleRate = BRF_SAMPLERATE_MAX;
m_ctrlTransferPage = 0;
String e;
e << "Unsupported USB speed " << m_devSpeed;
return setError(RadioInterface::InsufficientSpeed,error,e);
}
// Set I/O buffers
void BrfLibUsbDevice::initBuffers(bool* txSet, unsigned int totalSamples, unsigned int txMinSend)
{
totalSamples = clampInt(totalSamples,1024,16384,"buffered_samples",DebugConf);
unsigned int bufSamples = (speed() == LIBUSB_SPEED_HIGH) ? 252 : 508;
unsigned int nBuffs = totalSamples / bufSamples;
if (!nBuffs)
nBuffs = 1;
for (int tx = 1; tx > -1; tx--) {
if (txSet && *txSet != tx)
continue;
BrfDevIO& io = getIO(tx);
if (io.buffers == nBuffs && io.bufSamples == bufSamples)
continue;
// Lock I/O to make sure we don't use the buffers
BrfSerialize lck(this,tx,false);
String error;
for (unsigned int i = 0; !lck.devLocked() && i < 3; i++)
lck.wait(&error,1000000);
if (!lck.devLocked()) {
Debug(m_owner,DebugCrit,"Failed to initialize %s buffers: serialize [%p]",
brfDir(tx),m_owner);
continue;
}
bool first = !io.buffers;
io.resetSamplesBuffer(bufSamples,16,nBuffs);
String extra;
if (tx) {
if (txMinSend)
m_minBufsSend = clampInt(txMinSend,1,nBuffs,"tx_min_buffers",DebugConf);
else
m_minBufsSend = nBuffs;
extra << " tx_min_buffers=" << m_minBufsSend;
}
Debug(m_owner,first ? DebugAll : DebugInfo,
"Initialized I/O %s buffers=%u samples/buffer=%u total_bytes=%u%s [%p]",
brfDir(tx),io.buffers,io.bufSamples,io.buffer.length(),extra.safe(),m_owner);
lck.drop();
if (tx) {
// Regenerate TX pattern: it may have the same length as used buffers
Lock d(m_dbgMutex);
String pattern = m_state.m_txPattern;
m_state.m_txPattern = "";
float gain = m_state.m_txPatternGain;
d.drop();
setTxPattern(pattern,gain);
}
}
}
// Check timestamps before send / after read
void BrfLibUsbDevice::ioBufCheckTs(bool tx, unsigned int nBufs)
{
String invalid;
BrfDevIO& io = getIO(tx);
if (!nBufs)
nBufs = io.buffers;
unsigned int i = 0;
if (!io.lastTs) {
io.lastTs = io.bufTs(0);
i = 1;
}
unsigned int dontWarn = checkDbgInt(getIO(tx).dontWarnTs,nBufs);
for (; i < nBufs; i++) {
uint64_t crt = io.bufTs(i);
if (!dontWarn && (io.lastTs + io.bufSamples) != crt) {
if (!invalid)
invalid << ": invalid timestamps (buf=ts/delta)";
invalid << " " << (i + 1) << "=" << crt << "/" << (int64_t)(crt - io.lastTs);
}
if (dontWarn)
dontWarn--;
io.lastTs = crt;
}
if (invalid)
Debug(m_owner,DebugMild,"%s buf_samples=%u: %u buffers%s [%p]",
brfDir(tx),io.bufSamples,nBufs,invalid.safe(),m_owner);
}
void BrfLibUsbDevice::setIoDontWarnTs(bool tx)
{
BrfDevIO& io = getIO(tx);
Lock lck(m_dbgMutex);
io.dontWarnTs = io.buffers * 40;
XDebug(m_owner,DebugAll,"%s don't warn ts set to %d [%p]",
brfDir(tx),io.dontWarnTs,m_owner);
}
// Check samples limit before send / after read
void BrfLibUsbDevice::ioBufCheckLimit(bool tx, unsigned int nBufs)
{
BrfDevIO& io = getIO(tx);
if (!nBufs)
nBufs = io.buffers;
String invalid;
String tmp;
unsigned int check = 10;
for (unsigned int i = 0; i < nBufs; i++) {
int16_t* s = io.samples(i);
int16_t* e = io.samplesEOF(i);
for (unsigned int j = 0; check && s != e; s++, j++)
if (*s < -2048 || *s > 2047) {
invalid << tmp.printf(" %c=%d (%u at %u)",
brfIQ((j % 2) == 0),*s,i + 1,j / 2);
check--;
}
}
if (invalid)
Debug(m_owner,DebugConf,"%s: sample value out of range buffers=%u:%s [%p]",
brfDir(tx),nBufs,invalid.c_str(),m_owner);
}
void BrfLibUsbDevice::updateAlterData(const NamedList& params)
{
Lock lck(m_dbgMutex);
m_rxAlterDataParams = params;
m_rxAlterDataParams.assign("-");
m_rxAlterData = true;
}
void BrfLibUsbDevice::rxAlterData(bool first)
{
while (m_rxAlterDataParams.c_str()) {
Lock lck(m_dbgMutex);
if (!m_rxAlterDataParams.c_str())
break;
if (m_rxAlterDataParams.getBoolValue(YSTRING("rx_alter_increment"))) {
if (!m_rxAlterIncrement)
m_rxAlterIncrement = 1;
}
else
m_rxAlterIncrement = 0;
m_rxAlterData = (m_rxAlterIncrement != 0);
const String& tsJumpPattern = m_rxAlterDataParams[YSTRING("rx_alter_ts_jump_pattern")];
if (tsJumpPattern != m_rxAlterTsJumpPatern) {
m_rxAlterTsJumpPatern = tsJumpPattern;
ObjList* list = m_rxAlterTsJumpPatern.split(',');
m_rxAlterTsJump.overAlloc(10 * sizeof(int64_t));
m_rxAlterTsJump.resize(list->count() * sizeof(int64_t));
int64_t* d = (int64_t*)m_rxAlterTsJump.data();
bool ok = false;
unsigned int index = 0;
for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
const String* s = static_cast<String*>(o->get());
if (!s->startsWith("rep_")) {
d[index] = s->toInt64();
if (d[index])
ok = true;
index++;
continue;
}
int64_t lastVal = index ? d[index - 1] : 0;
unsigned int repeat = s->substr(4).toInteger(0,0,0);
if (repeat < 2) {
d[index++] = lastVal;
continue;
}
DataBlock tmp = m_rxAlterTsJump;
m_rxAlterTsJump.resize(tmp.length() + (sizeof(int64_t) * (repeat - 1)));
d = (int64_t*)m_rxAlterTsJump.data();
::memcpy(d,tmp.data(),index * sizeof(int64_t));
while (repeat--)
d[index++] = lastVal;
}
TelEngine::destruct(list);
if (!ok)
m_rxAlterTsJump.clear();
m_rxAlterTsJumpPos = 0;
}
m_rxAlterTsJumpSingle = m_rxAlterDataParams.getBoolValue(
YSTRING("rx_alter_ts_jump_single"),true);
if (m_rxAlterTsJump.length())
m_rxAlterData = true;
m_rxAlterDataParams.assign("");
m_rxAlterDataParams.clear();
if (!m_rxAlterData)
return;
}
BrfDevIO& io = m_rxIO;
if (first) {
// Change timestamps
if (m_rxAlterTsJump.length()) {
int64_t* d = (int64_t*)m_rxAlterTsJump.data();
unsigned int len = m_rxAlterTsJump.length() / sizeof(int64_t);
for (unsigned int i = 0; i < io.buffers; i++) {
if (d[m_rxAlterTsJumpPos])
io.setBufTs(i,io.bufTs(i) + d[m_rxAlterTsJumpPos]);
m_rxAlterTsJumpPos++;
if (m_rxAlterTsJumpPos >= len) {
m_rxAlterTsJumpPos = 0;
if (m_rxAlterTsJumpSingle) {
m_rxAlterTsJump.clear();
// Signal update on next call
m_rxAlterData = true;
break;
}
}
}
}
}
else {
// Change radio data
if (m_rxAlterIncrement && !first) {
for (unsigned int i = 0; i < io.buffers; i++) {
int16_t* p = io.samples(i);
int16_t* last = io.samplesEOF(i);
while (p != last) {
*p++ = m_rxAlterIncrement;
*p++ = -m_rxAlterIncrement;
m_rxAlterIncrement++;
if (m_rxAlterIncrement >= 2048)
m_rxAlterIncrement = 1;
}
}
}
}
#if 0
if (!first)
printIOBuffer(false,"alter");
#endif
}
unsigned int BrfLibUsbDevice::calLPFBandwidth(const BrfCalData& bak, uint8_t subMod,
uint8_t dcCnt, uint8_t& dcReg, String& e)
{
#ifdef DEBUG_DEVICE_AUTOCAL
Debugger d(DebugAll,"CAL PROC"," submod=%u dcCnt=0x%x [%p]",subMod,dcCnt,m_owner);
#endif
uint8_t data = 0;
unsigned int status = 0;
// Programing and Calibration Guide 4.5
// PLL Reference Clock Frequency == 40MHz?
if (s_freqRefClock != 40000000) {
// Power down TxVGA2 -- (is optional)
BRF_FUNC_CALL_RET(lmsSet(0x44,0x0c,0x0c,&e));
// Enable TxPPL Register 0x14 set bit 4 to 1
BRF_FUNC_CALL_RET(lmsSet(0x14,0x08,&e));
// Produce 320 MHz
// TODO FIXME The values are hard codded for 38.4 MHz as reference clock
BRF_FUNC_CALL_RET(lmsWrite(0x10,0x42,&e));
BRF_FUNC_CALL_RET(lmsWrite(0x11,0xaa,&e));
BRF_FUNC_CALL_RET(lmsWrite(0x12,0xaa,&e));
BRF_FUNC_CALL_RET(lmsWrite(0x13,0xaa,&e));
// TopSPI:CLKSEL_LPFCAL = 0
BRF_FUNC_CALL_RET(lmsReset(0x06,0x08,&e));
// Power up LPF tuning clock generation block TOPSPI:PD_CLKLPFCAL = 0
BRF_FUNC_CALL_RET(lmsReset(0x06,0x04,&e));
}
BRF_FUNC_CALL_RET(lmsRead(0x54,data,&e));
BRF_FUNC_CALL_RET(lmsSet(0x07,(data >> 2) & 0x0f,0x0f,&e));
// TopSPI:En_CAL_LPFCAL=1(enable)
BRF_FUNC_CALL_RET(lmsSet(0x07,0x80,&e));
// TopSPI:RST_CAL_LPFCAL=1 (RST active)
BRF_FUNC_CALL_RET(lmsSet(0x06,0x01,&e));
// Reset signal used at the beginning of calibration cycle.
// Reset signal needs to be longer than 100ns
Thread::msleep(1);
// TopSPI:RST_CAL_LPFCAL=0 (RST inactive)
BRF_FUNC_CALL_RET(lmsReset(0x06,0x01,&e));
// RCCAL = TopSPI::RCCAL_LPFCAL
BRF_FUNC_CALL_RET(lmsRead(0x01,data,&e));
dcReg = data >> 5;
BRF_FUNC_CALL_RET(lmsSet(0x56,dcReg << 4,0x70,&e));
DDebug(m_owner,DebugAll,"%s calibrated submodule %u -> %u [%p]",
bak.modName(),subMod,dcReg,m_owner);
return 0;
}
void BrfLibUsbDevice::dumpState(String& s, const NamedList& p, bool lockPub, bool force)
{
BrfSerialize txSerialize(this,true,false);
if (lockPub) {
txSerialize.wait(0,5000000);
if (txSerialize.status) {
if (RadioInterface::Failure == txSerialize.status)
s << "Failed to retrieve state: lock failed";
return;
}
}
String lmsModules, lpStatus, lms, lmsStr;
if (p.getBoolValue(YSTRING("dump_dev"),force)) {
BrfDevDirState& tx = getDirState(true);
BrfDevDirState& rx = getDirState(false);
s << " TX / RX";
s << "\r\nFREQ(Hz): " << tx.frequency << " / " << rx.frequency;
s << "\r\nVGA1: " << tx.vga1 << " / " << rx.vga1;
s << "\r\nVGA2: " << tx.vga2 << " / " << rx.vga2;
s << "\r\nSampleRate: " << tx.sampleRate << " / " << rx.sampleRate;
s << "\r\nFilter: " << tx.lpfBw << " / " << rx.lpfBw;
s << "\r\ntxpattern: " << m_state.m_txPattern;
s << "\r\nloopback: " << lookup(m_state.m_loopback,s_loopback);
if (force) {
s << "\r\nSerial: " << serial();
s << "\r\nSpeed: " << speedStr();
s << "\r\nFirmware: " << fwVerStr();
s << "\r\nFPGA: " << fpgaVerStr();
}
}
if (p.getBoolValue(YSTRING("dump_lms_modules"),force)) {
dumpLmsModulesStatus(&lmsModules);
s.append("LMS modules:","\r\n\r\n") << lmsModules;
}
if (p.getBoolValue(YSTRING("dump_loopback_status"),force)) {
dumpLoopbackStatus(&lpStatus);
s.append("Loopback switches:","\r\n\r\n") << lpStatus;
}
if (p.getBoolValue(YSTRING("dump_lms"),force)) {
internalDumpPeripheral(UartDevLMS,0,128,&lms,16);
s.append("LMS:","\r\n\r\n") << lms;
}
String readLms = p[YSTRING("dump_lms_str")];
if (readLms) {
if (readLms == "-")
lmsRead(lmsStr,0,false);
else {
bool interleaved = (readLms[0] == '+');
if (interleaved)
readLms = readLms.substr(1);
lmsRead(lmsStr,&readLms,interleaved);
}
s.append("LMS string:\r\n","\r\n\r\n") << lmsStr;
}
}
// LMS autocalibration
unsigned int BrfLibUsbDevice::calibrateAuto(String* error)
{
BrfSerialize txSerialize(this,true,false);
BrfSerialize rxSerialize(this,false,false);
unsigned int status = 0;
// Pause I/O threads if calibration is running
if (m_calibrateStatus == Calibrating) {
BRF_FUNC_CALL_RET(calThreadsPause(true,error));
}
if (!rxSerialize.devLocked()) {
BRF_FUNC_CALL_RET(rxSerialize.wait(error));
}
if (!txSerialize.devLocked()) {
BRF_FUNC_CALL_RET(txSerialize.wait(error));
}
#ifdef DEBUG_DEVICE_AUTOCAL
Debugger debug(DebugAll,"AUTOCALIBRATION"," '%s' [%p]",m_owner->debugName(),m_owner);
#endif
Debug(m_owner,DebugInfo,"LMS autocalibration starting ... [%p]",m_owner);
BrfDuration duration;
String e;
BrfDevState oldState(m_state,0,DevStatDc,DevStatDc);
// Set TX/RX DC I/Q to 0
BrfDevState set0(DevStatAbortOnFail,DevStatDc,DevStatDc);
status = setState(set0,&e);
int8_t calVal[BRF_CALIBRATE_LAST][BRF_CALIBRATE_MAX_SUBMODULES];
::memset(calVal,-1,sizeof(calVal));
for (int m = BRF_CALIBRATE_FIRST; !status && m <= BRF_CALIBRATE_LAST; m++) {
BrfCalData bak(m);
#ifdef DEBUG_DEVICE_AUTOCAL
Debugger d(DebugAll,"AUTOCALIBRATION"," module: %s [%p]",bak.modName(),m_owner);
#endif
if ((status = cancelled(&e)) != 0)
break;
Debug(m_owner,DebugAll,"Calibrating %s [%p]",bak.modName(),m_owner);
if ((status = calBackupRestore(bak,true,&e)) != 0)
break;
status = calInitFinal(bak,true,&e);
for (uint8_t subMod = 0; !status && subMod < bak.desc->subModules; subMod++) {
status = dcCalProcPrepare(bak,subMod,e);
if (!status) {
uint8_t dcReg = 0;
if (m == BRF_CALIBRATE_LPF_BANDWIDTH)
status = calLPFBandwidth(bak,subMod,31,dcReg,e);
else
status = dcCalProc(bak,subMod,31,dcReg,e);
if (!status) {
calVal[m][subMod] = dcReg;
status = dcCalProcPost(bak,subMod,dcReg,e);
}
}
if (status)
e.printf(2048,"Failed to calibrate module %s - %s",
bak.modName(),e.c_str());
}
unsigned int tmp = calInitFinal(bak,false,status ? 0 : &e);
if (!status)
status = tmp;
tmp = calBackupRestore(bak,false,status ? 0 : &e);
if (!status)
status = tmp;
if (status)
break;
Debug(m_owner,DebugAll,"Calibrated %s [%p]",bak.modName(),m_owner);
}
setState(oldState);
duration.stop();
if (status) {
e = "LMS autocalibration failed - " + e;
return showError(status,e,0,error);
}
String s;
#ifdef DEBUG
for (int m = BRF_CALIBRATE_FIRST; m <= BRF_CALIBRATE_LAST; m++) {
const BrfCalDesc& d = s_calModuleDesc[m];
String t;
if (d.subModules > 1)
for (uint8_t sm = 0; sm < d.subModules; sm++) {
t.printf("\r\n%s - %s: %d",calModName(m),d.subModName[sm],calVal[m][sm]);
s << t;
}
else
s << t.printf("\r\n%s: %d",calModName(m),calVal[m][0]);
}
#endif
Debug(m_owner,DebugInfo,"LMS autocalibration finished in %s [%p]%s",
duration.secStr(),m_owner,encloseDashes(s));
if (m_calibrateStatus != Calibrating)
return 0;
txSerialize.drop();
rxSerialize.drop();
return calThreadsPause(false,error);
}
unsigned int BrfLibUsbDevice::calBackupRestore(BrfCalData& bak, bool backup,
String* error)
{
const char* what = backup ? "backup" : "restore";
#ifdef DEBUG_DEVICE_AUTOCAL
Debugger d(DebugAll,"CAL BACKUP/RESTORE"," %s [%p]",what,m_owner);
#endif
unsigned int status = 0;
String e;
while (true) {
// We will backup the data in the CLK_EN register in case something goes wrong
status = lms(backup,0x09,bak.clkEn,&e);
if (status)
break;
if (bak.module == BRF_CALIBRATE_RX_LPF || bak.module == BRF_CALIBRATE_RX_VGA2) {
// BRF_FUNC_CALL_BREAK(lms(backup,0x71,bak.inputMixer,&e));
// BRF_FUNC_CALL_BREAK(lms(backup,0x7c,bak.loOpt,&e));
BRF_FUNC_CALL_BREAK(lnaGain(backup,bak.lnaGain,&e));
BRF_FUNC_CALL_BREAK(internalRxVga(backup,bak.rxVga1,true,&e));
if (bak.module == BRF_CALIBRATE_RX_VGA2) {
BRF_FUNC_CALL_BREAK(lms(backup,0x68,bak.rxVga2GainAB,&e));
}
status = internalRxVga(backup,bak.rxVga2,false,&e);
break;
}
if (bak.module == BRF_CALIBRATE_TX_LPF ||
bak.module == BRF_CALIBRATE_LPF_TUNING) {
DDebug(m_owner,DebugAll,"calBackupRestore: nothing to do for %s [%p]",
bak.modName(),this);
break;
}
if (bak.module == BRF_CALIBRATE_LPF_BANDWIDTH) {
BRF_FUNC_CALL_BREAK(lms(backup,0x06,bak.clkLPFCAL,&e));
BRF_FUNC_CALL_BREAK(lms(backup,0x07,bak.enLPFCAL,&e));
BRF_FUNC_CALL_BREAK(lms(backup,0x14,bak.txPPL,&e));
BRF_FUNC_CALL_BREAK(lms(backup,0x44,bak.txVGA2PwAmp,&e));
BRF_FUNC_CALL_BREAK(lms(backup,0x10,bak.nInt,&e));
BRF_FUNC_CALL_BREAK(lms(backup,0x11,bak.nFrac1,&e));
BRF_FUNC_CALL_BREAK(lms(backup,0x12,bak.nFrac2,&e));
BRF_FUNC_CALL_BREAK(lms(backup,0x13,bak.nFrac3,&e));
break;
}
status = setUnhandled(e,bak.module,"module");
break;
}
if (status == 0)
return 0;
e.printf(2048,"Failed to %s calibration data for module %s - %s",
what,bak.modName(),e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::calInitFinal(BrfCalData& bak, bool init, String* error)
{
const char* what = init ? "initialize" : "finalize";
#ifdef DEBUG_DEVICE_AUTOCAL
String lmsDump;
#if 0
if (init) {
internalDumpPeripheral(UartDevLMS,0,128,&lmsDump,16);
encloseDashes(lmsDump);
}
#endif
Debugger d(DebugAll,"CAL INIT/FINAL"," %s [%p]%s",what,m_owner,lmsDump.safe());
#endif
String e;
unsigned int status = 0;
while (true) {
// Enable the appropriate CLK_EN bit
if (init)
status = lmsWrite(0x09,bak.clkEn | bak.desc->clkEnMask,&e);
if (status)
break;
if (bak.module == BRF_CALIBRATE_LPF_TUNING ||
bak.module == BRF_CALIBRATE_LPF_BANDWIDTH) {
DDebug(m_owner,DebugAll,"calInitFinal(%s): nothing to do for %s [%p]",
what,bak.modName(),this);
break;
}
// Enable special conditions
if (bak.module == BRF_CALIBRATE_RX_LPF || bak.module == BRF_CALIBRATE_RX_VGA2) {
if (bak.module == BRF_CALIBRATE_RX_VGA2) {
// Set RXVGA2 DECODE on init/finalize
if (!init)
BRF_FUNC_CALL_BREAK(setRxVga2Decode(true,&e));
// TODO: Check it BRF_FUNC_CALL_BREAK(lmsChangeMask(0x63,0xc0,!init,&e));
}
else {
// FAQ 5.26 (rev 1.0r13) DC comparators should be
// powered up before calibration and then powered down after it
BRF_FUNC_CALL_BREAK(lmsChangeMask(0x5f,0x80,!init,&e));
if (init) {
BRF_FUNC_CALL_BREAK(lmsSet(0x56,0x04,&e));
}
else {
BRF_FUNC_CALL_BREAK(lmsReset(0x56,0x04,&e));
}
}
// Done for finalize
if (!init)
break;
#if 0
// TODO: Check it !!! It is really necessary ?
// Connect LNA to the external pads and internally terminate
status = lmsWrite2(0x71,bak.inputMixer & 0x7f,0x7c,bak.loOpt | 0x04,&e);
if (status)
break;
#endif
// FAQ 4.2 (rev 1.0r13): Attempt to calibrate RX at max gain
BRF_FUNC_CALL_BREAK(lnaGainSet(LnaGainMax,&e));
BRF_FUNC_CALL_BREAK(internalSetRxVga(BRF_RXVGA1_GAIN_MAX,true,&e));
BRF_FUNC_CALL_BREAK(internalSetRxVga(BRF_RXVGA2_GAIN_MAX,false,&e));
if (bak.module == BRF_CALIBRATE_RX_VGA2)
status = setRxVga2Decode(true,&e);
break;
}
if (bak.module == BRF_CALIBRATE_TX_LPF) {
// TX_DACBUF_PD (TX data DAC buffers)
// LMS6002 Quick starter manual, Section 6.1
// No signal should be applied to DACs (power down: bit is 1)
// PD_DCOCMP_LPF (DC offset comparator of DC offset cancellation)
// It must be powered down (bit set to 1) when calibrating
if (init) {
//BRF_FUNC_CALL_BREAK(lmsSet(0x36,0x80,&e));
BRF_FUNC_CALL_BREAK(lmsSet(0x36,0x04,&e));
BRF_FUNC_CALL_BREAK(lmsReset(0x3f,0x80,&e));
}
else {
//BRF_FUNC_CALL_BREAK(lmsReset(0x36,0x80,&e));
BRF_FUNC_CALL_BREAK(lmsReset(0x36,0x04,&e));
BRF_FUNC_CALL_BREAK(lmsSet(0x3f,0x80,&e));
}
break;
}
status = setUnhandled(e,bak.module,"module");
break;
}
if (status == 0)
return 0;
e.printf(2048,"Failed to %s calibration for module %s - %s",
what,bak.modName(),e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::dcCalProcPrepare(const BrfCalData& bak, uint8_t subMod,
String& e)
{
#ifdef DEBUG_DEVICE_AUTOCAL
Debugger d(DebugAll,"CAL PREPARE"," submod=%u [%p]",subMod,m_owner);
#endif
if (bak.module != BRF_CALIBRATE_RX_VGA2)
return 0;
// Prepare RX VGA2 calibration
if (subMod > 4)
return setUnhandled(e,subMod,"submodule");
// RXVGA2 DC REF module (subMod 0)
// Set RXVGA2GAIN A and B to default values
if (subMod == 0)
return lmsWrite(0x68,0x01,&e);
// VGA2 A/B I/Q channels
// Set DECODE bit to direct signal on start
if (subMod == 1) {
unsigned int status = setRxVga2Decode(false,&e);
if (status)
return status;
}
// subMod 1: set RXVGA2GAIN A=18dB and B=0
// subMod 3: set RXVGA2GAIN A=0 and B=18dB
if (subMod == 1 || subMod == 3)
return lmsWrite(0x68,(subMod == 1) ? 0x06 : 0x60,&e);
return 0;
}
unsigned int BrfLibUsbDevice::dcCalProc(const BrfCalData& bak, uint8_t subMod,
uint8_t dcCnt, uint8_t& dcReg, String& e)
{
#ifdef DEBUG_DEVICE_AUTOCAL
Debugger d(DebugAll,"CAL PROC"," submod=%u dcCnt=0x%x [%p]",subMod,dcCnt,m_owner);
#endif
// Set active calibration module address
uint8_t data = 0;
unsigned int status = 0;
BRF_FUNC_CALL_RET(lmsRead(bak.desc->addr + 3,data,&e));
data &= ~(0x07);
data |= subMod & 0x07;
BRF_FUNC_CALL_RET(lmsWrite(bak.desc->addr + 3,data,&e));
// Set CNTVAL
BRF_FUNC_CALL_RET(lmsWrite(bak.desc->addr + 2,dcCnt & 0x1f,&e));
// DC_LOAD: Auto load DC_CNTVAL (1: load, 0: don't load)
data |= 0x10;
BRF_FUNC_CALL_RET(lmsWrite(bak.desc->addr + 3,data,&e));
// Disable auto load of DC_CNTVAL, just in case something goes wrong
data &= ~0x10;
BRF_FUNC_CALL_RET(lmsWrite(bak.desc->addr + 3,data,&e));
uint8_t clbrStart = data | 0x20;
uint8_t clbrStop = data & ~0x20;
// See Section 4.1: General DC calibration procedure
bool first = true;
while (true) {
// Calibrate
// Enable and disable DC_START_CLBR
BRF_FUNC_CALL_RET(lmsWrite2(bak.desc->addr + 3,clbrStart,bak.desc->addr + 3,clbrStop,&e));
// We should wait for 6.4 us for calibration to end
Thread::msleep(1);
dcReg = 0xff;
for (unsigned int i = 0; i < 30; i++) {
String tmp;
BRF_FUNC_CALL_RET(cancelled(&e));
// Poll for DC_CLBR_DONE
status = lmsRead(bak.desc->addr + 1,data,&tmp);
if (status) {
Debug(m_owner,DebugMild,"%s [%p]",e.c_str(),m_owner);
status = 0;
continue;
}
if ((data & 0x02) != 0)
continue;
// Read DC_REG
BRF_FUNC_CALL_RET(lmsRead(bak.desc->addr,data,&e));
dcReg = (data & 0x3f);
break;
}
if (dcReg == 0xff)
return setError(RadioInterface::Failure,&e,"Calibration loop timeout");
if (first) {
if (dcReg != 31)
break;
first = false;
continue;
}
if (dcReg == 0) {
e << "Algorithm does not converge for submodule " << subMod;
return RadioInterface::Failure;
}
break;
}
DDebug(m_owner,DebugAll,"%s calibrated submodule %u -> %u [%p]",
bak.modName(),subMod,dcReg,m_owner);
return 0;
}
unsigned int BrfLibUsbDevice::dcCalProcPost(const BrfCalData& bak, uint8_t subMod,
uint8_t dcReg, String& e)
{
#ifdef DEBUG_DEVICE_AUTOCAL
Debugger d(DebugAll,"CAL PROC POST"," submod=%u dcReg=0x%x [%p]",subMod,dcReg,m_owner);
#endif
unsigned int status = 0;
if (bak.module == BRF_CALIBRATE_LPF_TUNING) {
// Set DC_REG in TX/RX LPF DCO_DACCAL
uint8_t addr[] = {0x55,0x35};
for (uint8_t i = 0; !status && i < sizeof(addr); i++)
status = lmsSet(addr[i],dcReg,0x3f,&e);
if (status)
e.printf("Failed to set DCO_DACCAL - %s",e.c_str());
}
return status;
}
unsigned int BrfLibUsbDevice::calibrateBbCorrection(BrfBbCalData& data,
int corr, int range, int step, int pass, String* error)
{
static const int corrPeer[CorrCount] = {CorrLmsQ, CorrLmsI,
CorrFpgaGain, CorrFpgaPhase};
static const unsigned int syncFlags[CorrCount] = {DevStatDcI, DevStatDcQ,
DevStatFpgaPhase, DevStatFpgaGain};
int* corrVal[CorrCount] = {&data.m_dcI, &data.m_dcQ, &data.m_phase, &data.m_gain};
BrfDevDirState& t = m_syncTxState.m_tx;
int* syncSet[CorrCount] = {&t.dcOffsetI, &t.dcOffsetQ, &t.fpgaCorrPhase, &t.fpgaCorrGain};
bool dc = (CorrLmsI == corr || CorrLmsQ == corr);
if (!dc && CorrFpgaPhase != corr && CorrFpgaGain != corr)
return setErrorFail(error,"calibrateBbCorrection: unhandled corr");
BrfDuration duration;
// Set peer (fixed) correction
*syncSet[corrPeer[corr]] = *corrVal[corrPeer[corr]];
unsigned int status = setStateSyncTx(syncFlags[corrPeer[corr]],error);
// Set calibration range
int minV = dc ? BRF_TX_DC_OFFSET_MIN : -BRF_FPGA_CORR_MAX;
int maxV = dc ? BRF_TX_DC_OFFSET_MAX : BRF_FPGA_CORR_MAX;
int calVal = *corrVal[corr] - range;
int calValMax = *corrVal[corr] + range;
if (calVal < minV)
calVal = minV;
if (calValMax > maxV)
calValMax = maxV;
Debug(m_owner,DebugNote,"Calibrating %s pass=%d [%p]",
lookup(corr,s_corr),pass,this);
unsigned int trace = data.uintParam(dc,"trace");
if (trace)
Output("Pass #%u calibrating %s (crt: %d) %s=%d "
"samples=%u range=%d step=%d interval=[%d..%d]",
pass,lookup(corr,s_corr),*corrVal[corr],
lookup(corrPeer[corr],s_corr),*corrVal[corrPeer[corr]],
data.samples(),range,step,calVal,calValMax);
bool traceRepeat = trace && data.boolParam(dc,"trace_repeat",true);
bool traceFailed = trace && data.boolParam(dc,"trace_failed",true);
bool accum = false;
if (data.m_dump.valid()) {
data.dumpCorrStart(pass,corr,*corrVal[corr],corrPeer[corr],
*corrVal[corrPeer[corr]],range,step,calVal,calValMax);
accum = (0 != data.m_calAccum.data.length());
data.m_dump.resetDumpOkFail();
}
float totalStop = data.m_params.getDoubleValue("stop_total_threshold",BRF_MAX_FLOAT);
float limit = getSampleLimit(data.m_params,1);
const char* waitReason = 0;
// Allow TX/RX threads to properly start and synchronize
Thread::msleep(100);
data.prepareCalculate();
int dumpTx = data.intParam(dc,"trace_dump_tx");
BrfBbCalDataResult* res = new BrfBbCalDataResult[data.m_repeatRxLoop];
unsigned int i = 0;
// Disable DC/FPGA change debug message
unsigned int& showCorrChange = dc ? m_state.m_tx.showDcOffsChange :
m_state.m_tx.showFpgaCorrChange;
showCorrChange++;
if (!dc)
m_state.m_tx.showPowerBalanceChange++;
uint64_t ts = 0;
uint64_t tsOffs = m_radioCaps.rxLatency;
if (!dc)
tsOffs += m_radioCaps.txLatency;
for (; !status && calVal <= calValMax; calVal += step) {
i = 0;
*syncSet[corr] = calVal;
BRF_FUNC_CALL_BREAK(setStateSyncTx(syncFlags[corr],error));
ts = m_syncTxState.m_tx.m_timestamp + tsOffs;
bool ok = false;
for (; i < data.m_repeatRxLoop; ++i) {
res[i].status = 0;
if (traceRepeat && i) {
String s;
Output(" REPEAT[%u/%u] [%10s] %s=%-5d %s",i + 1,data.m_repeatRxLoop,
String(ts).c_str(),lookup(corr,s_corr),
calVal,data.dump(s,res[i-1]).c_str());
}
if (dumpTx) {
if (dumpTx > 0)
showBuf(true,dumpTx,false);
else
showBuf(true,-dumpTx,true);
}
ts += data.samples();
BRF_FUNC_CALL_BREAK(capture(false,data.buf(),data.samples(),ts,error));
if (m_calibrateStop)
break;
if (trace > 4)
showBuf(false,trace - 4,false);
ok = data.calculate(res[i]);
status = checkSampleLimit(data.buf(),data.samples(),limit,error);
if (status) {
data.m_dump.appendFormatted(data.buffer(),false);
if (trace) {
String s;
data.dump(s,true);
Output(" %s=%-5d [%10s] %s\tSAMPLE OUT OF RANGE",
lookup(corr,s_corr),calVal,String(ts).c_str(),s.c_str());
}
res[i].status = status;
if (i == (data.m_repeatRxLoop - 1))
break;
status = 0;
if (error)
error->clear();
continue;
}
if (data.m_dump.valid() &&
((ok && data.m_dump.dumpOk()) || (!ok && data.m_dump.dumpFail())))
data.m_dump.appendFormatted(data.buffer(),ok);
res[i].status = ok ? 0 : RadioInterface::Failure;
if (ok)
break;
}
if (status || m_calibrateStop)
break;
if (i >= data.m_repeatRxLoop)
i = data.m_repeatRxLoop - 1;
data.setResult(res[i]);
bool better = (data.m_best > data.m_cal.value);
if (accum) {
data.m_calAccum.append(data.m_cal);
data.m_testAccum.append(data.m_test);
data.m_totalAccum.append(data.m_total);
}
if (trace) {
String s;
if (trace > 1 && ok && (better || trace > 2))
data.dump(s,trace > 2);
else if (!ok && traceFailed)
data.dump(s,true);
if (s)
Output(" %s=%-5d [%10s] %s%s",lookup(corr,s_corr),calVal,
String(ts).c_str(),s.c_str(),better ? "\tBEST" : "");
}
if (!ok && data.m_stopOnRecvFail) {
if (data.m_stopOnRecvFail < 0)
waitReason = "Recv data check failure";
res[i].status = status = setErrorFail(error,"Recv data check failure");
break;
}
if (totalStop < data.m_total) {
waitReason = "Total error threshold reached";
res[i].status = status = setErrorFail(error,waitReason);
break;
}
// Update best values
if (better) {
data.m_best = data.m_cal;
*corrVal[corr] = calVal;
}
}
// Print last failures if we stopped due to data check failure
if (status && !m_calibrateStop && status != RadioInterface::Cancelled &&
(i == data.m_repeatRxLoop || res[i].status)) {
String s;
if (i < data.m_repeatRxLoop)
i++;
for (unsigned int j = 0; j < i; j++) {
BrfBbCalDataResult& r = res[j];
String tmp;
s << tmp.printf(512,"\r\ntest_tone=%f total=%f test/total=%.2f cal_tone=%f cal/test=%.2f",
r.test,r.total,r.test_total,r.cal,r.cal_test);
if (r.status == RadioInterface::Saturation)
s << " (Sample out of range)";
else if (r.status) {
if (error)
s << " (" << *error << ")";
else
s << " (" << r.status << " " << RadioInterface::errorName(r.status) << ")";
}
}
Debug(owner(),DebugWarn,"BB Calibration (%s) stopping on data check failure."
" Signal values (test/total interval=(0.5-1]): [%p]\r\n-----%s\r\n-----",
lookup(corr,s_corr),this,s.c_str());
}
delete[] res;
showCorrChange--;
if (!dc)
m_state.m_tx.showPowerBalanceChange--;
duration.stop();
if (trace)
Output(" %d/%d [%s]: min/max - cal=%f/%f test=%f/%f total=%f/%f test/total=%.2f/%.2f",
(dc ? data.m_dcI : data.m_phase),(dc ? data.m_dcQ : data.m_gain),
duration.secStr(),
data.m_cal.min,data.m_cal.max,data.m_test.min,data.m_test.max,
data.m_total.min,data.m_total.max,data.m_test_total.min,data.m_test_total.max);
if (data.m_dump.valid())
data.dumpCorrEnd(dc);
if (waitReason)
return waitCancel("Calibration stopped",waitReason,error);
return status;
}
unsigned int BrfLibUsbDevice::prepareCalibrateBb(BrfBbCalData& data, bool dc,
String* error)
{
Debug(m_owner,DebugAll,"prepareCalibrateBb dc=%d [%p]",dc,this);
// Reset cal structure
unsigned int status = 0;
while (true) {
BRF_FUNC_CALL_BREAK(isInitialized(true,true,error));
unsigned int flags = DevStatFreq | DevStatLpfBw | DevStatSampleRate | DevStatVga;
BrfDevState s(DevStatAbortOnFail | DevStatLoopback,flags,flags);
s.m_tx.frequency = data.m_tx.frequency;
s.m_tx.lpfBw = data.m_tx.lpfBw;
s.m_tx.sampleRate = data.m_tx.sampleRate;
data.m_calFreq = data.m_tx.frequency;
data.m_calSampleRate = data.m_tx.sampleRate;
unsigned int rxFreq = 0;
unsigned int Fs = data.m_calSampleRate;
unsigned int bw = data.m_rx.sampleRate;
// Prepare device
if (dc) {
// TX/RX frequency difference MUST be greater than 1MHz to avoid interferences
// rxFreq = FreqTx - (Fs / 4)
// Choose Fs (RX sample rate):
// Fs > TxSampleRate
// Fs / 4 > 1MHz => Fs > 4MHz
if (Fs < 4000000) {
Fs = 4001000;
bw = 3840000;
}
else {
unsigned int delta = data.uintParam(dc,"samplerate_delta",10000);
if (delta) {
Fs += delta;
// Round up to a multiple of 4 to avoid division errors
if ((Fs % 4) != 0)
Fs = Fs + 4 - (Fs % 4);
}
// Choose next upper filter bandwidth after TX
uint8_t bwIndex = bw2index(data.m_tx.lpfBw + 1);
bw = index2bw(bwIndex);
if (bw <= data.m_tx.lpfBw) {
// !!! OOPS !!!
return setErrorFail(error,"Unable to choose RX filter bandwidth");
}
}
// cal, test
// For DC, test and cal differ by pi/2
// FIXME - This works only for RX and TX same sample rate.
rxFreq = data.m_tx.frequency - (Fs / 4);
data.resetOmega(-M_PI_2,-M_PI);
}
else {
// parameters for Gain/Phase calibration
// cal, test
// For phase/gain, test and cal differ by pi
// FIXME - This works only for RX and TX same sample rate.
rxFreq = data.m_tx.frequency + (Fs / 4);
data.resetOmega(M_PI,0);
}
s.m_tx.lpfBw = bw;
s.m_tx.sampleRate = Fs;
s.m_rx.lpfBw = bw;
s.m_rx.sampleRate = Fs;
s.m_rx.frequency = rxFreq;
s.m_tx.vga1 = data.intParam(dc,YSTRING("txvga1"),
BRF_TXVGA1_GAIN_DEF,BRF_TXVGA1_GAIN_MIN,BRF_TXVGA1_GAIN_MAX);
s.m_tx.vga2 = data.intParam(dc,YSTRING("txvga2"),
20,BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX);
s.m_rx.vga1 = data.intParam(dc,YSTRING("rxvga1"),
BRF_RXVGA1_GAIN_DEF,BRF_RXVGA1_GAIN_MIN,BRF_RXVGA1_GAIN_MAX);
s.m_rx.vga2 = data.intParam(dc,YSTRING("rxvga2"),
BRF_RXVGA2_GAIN_DEF,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX);
if (dc) {
m_syncTxState.m_tx.fpgaCorrPhase = data.m_phase;
m_syncTxState.m_tx.fpgaCorrGain = data.m_gain;
s.m_tx.fpgaCorrPhase = data.m_phase;
s.m_tx.fpgaCorrGain = data.m_gain;
s.m_txChanged |= DevStatFpga;
}
else {
m_syncTxState.m_tx.dcOffsetI = data.m_dcI;
m_syncTxState.m_tx.dcOffsetQ = data.m_dcQ;
s.m_tx.dcOffsetI = data.m_dcI;
s.m_tx.dcOffsetQ = data.m_dcQ;
s.m_txChanged |= DevStatDc;
}
NamedList lpParams("");
lpParams.copySubParams(data.m_params,YSTRING("loopback_"));
int defLp = brfIsLowBand(s.m_tx.frequency) ? LoopRfLna1 : LoopRfLna2;
int lp = data.m_params.getIntValue(YSTRING("loopback"),s_loopback,defLp);
s.setLoopback(lp,lpParams);
// Stop I/O threads (use internal functions to set params)
BRF_FUNC_CALL_BREAK(calThreadsPause(true,error));
BRF_FUNC_CALL_BREAK(setState(s,error));
// RX buffers may change: adjust it in cal data!
unsigned int samples = getRxSamples(data.m_params);
if (samples != data.samples())
data.resetBuffer(samples);
// Toggle timestamps (reset FPGA timestamps)
enableRfFpgaBoth(false);
enableTimestamps(false);
Thread::msleep(50);
BRF_FUNC_CALL_BREAK(enableTimestamps(true,error));
BRF_FUNC_CALL_BREAK(enableRfFpgaBoth(true,error));
BRF_FUNC_CALL_BREAK(calThreadsPause(false,error));
return 0;
}
return status;
}
unsigned int BrfLibUsbDevice::calibrateBb(BrfBbCalData& data, bool dc, String* error)
{
const char* oper = dc ? "TX I/Q DC Offset (LO Leakage)" :
"TX I/Q Imbalance";
Debug(m_owner,DebugAll,"calibrateBb %s [%p]",oper,this);
// VGA tests
String e;
unsigned int status = testVgaCheck(data.m_params,oper,data.omega(false),&e,
data.prefix(dc));
if (status) {
e.printf(2048,"%s failed - %s",oper,e.c_str());
return showError(status,e,0,error);
}
// FIXME: testing
if (data.boolParam(dc,"disable"))
return 0;
// Prepare file dump
m_dbgMutex.lock();
String fName = dc ? m_bbCalDcFile : m_bbCalImbalanceFile;
m_dbgMutex.unlock();
data.initCal(*this,dc,fName);
int level = DebugNote;
bool dbg = m_owner && m_owner->debugAt(level);
if (dbg || data.uintParam(dc,"trace")) {
String s;
if (data.boolParam(dc,"dump_status_start"))
dumpState(s,data.m_params,true);
if (dbg)
Debug(m_owner,level,"%s calibration starting [%p]%s",oper,m_owner,encloseDashes(s,true));
else
Output("%s calibration starting omega_cal=%f omega_test=%f [%p]%s",
oper,data.omega(true),data.omega(false),m_owner,encloseDashes(s,true));
}
BrfDuration duration;
int range = dc ? (BRF_TX_DC_OFFSET_MAX + 1) : BRF_FPGA_CORR_MAX;
unsigned int loops = data.uintParam(dc,"loops",2,1,10);
int step = dc ? 1 : 16*(1 << loops);
unsigned int origSamples = 0;
if (data.boolParam(dc,"increase_buffer",true))
origSamples = data.samples();
int corr1 = dc ? CorrLmsI : CorrFpgaPhase;
int corr2 = dc ? CorrLmsQ : CorrFpgaGain;
for (unsigned int pass = 1; !status && (range > 1) && pass <= loops; pass++) {
BRF_FUNC_CALL_BREAK(calibrateBbCorrection(data,corr1,range,step,pass,&e));
if (m_calibrateStop)
break;
BRF_FUNC_CALL_BREAK(calibrateBbCorrection(data,corr2,range,step,pass,&e));
if (m_calibrateStop)
break;
range >>= 1;
step >>= 1;
if (!step || pass == (loops - 1))
step = 1;
if (origSamples)
data.resetBuffer(data.samples() * 2);
}
if (origSamples)
data.resetBuffer(origSamples);
duration.stop();
String result;
if (!status) {
if (dc)
result << "I=" << data.m_dcI << " " << "Q=" << data.m_dcQ;
else
result << "PHASE=" << data.m_phase << " " << "GAIN=" << data.m_gain;
Debug(m_owner,level,"%s calibration finished in %s %s [%p]",
oper,duration.secStr(),result.c_str(),m_owner);
}
// Dump result to file
data.finalizeCal(result);
// Wait for cancel ?
if (!status && dc && !m_calibrateStop) {
const String& i = data.m_params[YSTRING("stop_dc_i_out_of_range")];
if (i && !isInterval(data.m_dcI,BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX,i))
status = waitCancel("Calibration stopped","DC I " +
String(data.m_dcI) + " out of range " + i,&e);
else {
const String& q = data.m_params[YSTRING("stop_dc_q_out_of_range")];
if (q && !isInterval(data.m_dcQ,BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX,q))
status = waitCancel("Calibration stopped","DC Q " +
String(data.m_dcQ) + " out of range " + q,&e);
}
}
if (!status)
return 0;
e.printf(2048,"%s failed - %s",oper,e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::calibrateBaseband(String* error)
{
Configuration cfg;
loadCfg(&cfg,false);
NamedList& p = *cfg.createSection(YSTRING("calibrate-bb"));
Debug(m_owner,DebugInfo,"Baseband calibration starting ... [%p]",m_owner);
BrfDuration duration;
m_calibrateStop = 0;
String e;
unsigned int status = 0;
unsigned int chg = DevStatLoopback | DevStatTxPattern;
unsigned int dirChg = DevStatFreq | DevStatSampleRate | DevStatVga | DevStatLpfBw;
BrfDevState oldState(m_state,chg,dirChg,dirChg);
setTxPattern(p.getValue(YSTRING("txpattern"),"circle"));
BrfBbCalData data(getRxSamples(p),p);
data.m_tx = m_state.m_tx;
data.m_rx = m_state.m_rx;
while (status == 0) {
m_calibration.assign("");
m_calibration.clearParams();
BRF_FUNC_CALL_BREAK(writeLMS(p[YSTRING("lms_write")],&e,true));
//
// Calibrate TX LO Leakage (I/Q DC Offset)
BRF_FUNC_CALL_BREAK(prepareCalibrateBb(data,true,&e));
BRF_FUNC_CALL_BREAK(writeLMS(p[YSTRING("lms_write_alter")],&e,true));
for (int n = data.intParam(true,"repeat",1,1); n && !m_calibrateStop; n--) {
data.m_dcI = data.m_dcQ = 0;
BRF_FUNC_CALL_BREAK(calibrateBb(data,true,&e));
}
if (status || m_calibrateStop) {
Debug(m_owner,DebugInfo,"Calibration stopping with status=%d stop=%d [%p]",
status,m_calibrateStop,this);
break;
}
// Calibrate TX I/Q Imbalance
// This will set TX DC I/Q also
// test pattern and tuning data must change
BRF_FUNC_CALL_BREAK(prepareCalibrateBb(data,false,&e));
BRF_FUNC_CALL_BREAK(calibrateBb(data,false,&e));
//
// and do it all again
// LO leakage
if (status || m_calibrateStop) {
Debug(m_owner,DebugInfo,"Calibration stopping with status=%d stop=%d [%p]",
status,m_calibrateStop,this);
break;
}
BRF_FUNC_CALL_BREAK(prepareCalibrateBb(data,true,&e));
BRF_FUNC_CALL_BREAK(writeLMS(p[YSTRING("lms_write_alter")],&e,true));
BRF_FUNC_CALL_BREAK(calibrateBb(data,true,&e));
// I/Q balance
if (status || m_calibrateStop) {
Debug(m_owner,DebugInfo,"Calibration stopping with status=%d stop=%d [%p]",
status,m_calibrateStop,this);
break;
}
BRF_FUNC_CALL_BREAK(prepareCalibrateBb(data,false,&e));
BRF_FUNC_CALL_BREAK(calibrateBb(data,false,&e));
// Update calibrated data
// Use initial tunning values: we may change them during calibration
m_calibration.addParam("frequency",String(oldState.m_tx.frequency));
m_calibration.addParam("samplerate",String(oldState.m_tx.sampleRate));
m_calibration.addParam("filter",String(oldState.m_tx.lpfBw));
m_calibration.addParam("cal_tx_dc_i",String(data.m_dcI));
m_calibration.addParam("cal_tx_dc_q",String(data.m_dcQ));
m_calibration.addParam("cal_tx_fpga_corr_phase",String(data.m_phase));
m_calibration.addParam("cal_tx_fpga_corr_gain",String(data.m_gain));
break;
}
Debug(m_owner,DebugAll,"Finalizing BB calibration [%p]",m_owner);
// amplifier linearization
#if 0
static const float startSweep = -20;
static const float stopSweep = 0;
static const float stepSweep = 1.0;
ComplexVector sweep = sweepPower(startSweep, stopSweep, stepSweep);
if (sweep.length()) {
String tmp;
sweep.dump(tmp,Math::dumpComplex," ","(%g,%g)");
Debug(m_owner,DebugInfo,"amp sweep: %s [%p]",tmp.c_str(),this);
findGainExpParams(sweep, startSweep, stepSweep);
findPhaseExpParams(sweep, startSweep, stepSweep);
calculateAmpTable();
}
else
Debug(m_owner,DebugWarn,"amplifier calibration sweep failed");
#endif
if (m_calibrateStop) {
bool a = (m_calibrateStop < 0);
m_calibrateStop = 0;
Output("Calibration stopped: %s",(a ? "abort, no restore" : "restoring state"));
if (a)
return status;
}
calThreadsPause(true);
if (!status) {
oldState.m_tx.dcOffsetI = data.m_dcI;
oldState.m_tx.dcOffsetQ = data.m_dcQ;
oldState.m_tx.fpgaCorrPhase = data.m_phase;
oldState.m_tx.fpgaCorrGain = data.m_gain;
oldState.m_txChanged |= DevStatDc | DevStatFpga;
oldState.m_changed |= DevStatAbortOnFail;
status = setState(oldState,&e);
}
else
setState(oldState);
writeLMS(p[YSTRING("lms_write_post")],0,true);
duration.stop();
if (status == 0) {
String tmp;
m_calibration.dump(tmp,"\r\n");
Debug(m_owner,DebugNote,"Baseband calibration ended in %s [%p]%s",
duration.secStr(),m_owner,encloseDashes(tmp,true));
return 0;
}
e.printf(1024,"BB calibration failed: %s",e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::loopbackCheck(String* error)
{
Configuration cfg;
loadCfg(&cfg,false);
NamedList& p = *cfg.createSection(YSTRING("loopback-check"));
// Prepare data dump
m_dbgMutex.lock();
BrfDumpFile dump(&p,m_devCheckFile);
m_dbgMutex.unlock();
Debug(m_owner,DebugNote,"Loopback check starting ... [%p]",m_owner);
BrfDuration duration;
String e;
unsigned int status = 0;
unsigned int chg = DevStatLoopback | DevStatTxPattern;
unsigned int dirChg = DevStatFreq | DevStatVga | DevStatLpfBw | DevStatSampleRate;
BrfDevState oldState(m_state,chg,dirChg,dirChg);
setTxPattern(p.getValue(YSTRING("txpattern"),"circle"));
while (status == 0) {
unsigned int txFreq = p.getIntValue(YSTRING("txfrequency"),
m_state.m_tx.frequency,0);
if (!txFreq)
BRF_FUNC_CALL_BREAK(setErrorFail(&e,"Frequency not set"));
unsigned int nBuffs = p.getIntValue("buffers",10,1);
unsigned int bw = m_state.m_tx.lpfBw;
unsigned int sampleRate = m_state.m_tx.sampleRate;
bw = p.getIntValue(YSTRING("bandwidth"),bw ? bw : 1500000,1500000);
sampleRate = p.getIntValue(YSTRING("samplerate"),
sampleRate ? sampleRate : 2166667,2166667);
if (!sampleRate)
BRF_FUNC_CALL_BREAK(setErrorFail(&e,"Sample rate not set"));
// deltaFreq = RxFreq - TxFreq
// deltaFreq MUST be
// (1) 1MHz < deltaFreq < (sampleRate / 2)
// (2) deltaFreq != (sampleRate / 4)
// Sampling rate MUST be greater than 2MHz
unsigned int minDeltaFreq = 1000001;
unsigned int maxDeltaFreq = sampleRate / 2 - 1;
unsigned int deltaFreq = p.getIntValue(YSTRING("delta_freq"),
minDeltaFreq + (maxDeltaFreq - minDeltaFreq) / 2,minDeltaFreq,maxDeltaFreq);
if (deltaFreq == (sampleRate / 4)) {
// TODO: Properly adjust it
Debug(m_owner,DebugStub,"Loopback check adjusting delta freq [%p]",m_owner);
deltaFreq += 1000;
}
// Sanity check
if (deltaFreq <= 1000000 || (deltaFreq >= (sampleRate / 2)) ||
(deltaFreq == (sampleRate / 4))) {
e.printf("Invalid delta freq %u samplerate=%u",deltaFreq,sampleRate);
status = RadioInterface::Failure;
break;
}
unsigned int rxFreq = txFreq + deltaFreq;
// Prepare device
unsigned int flags = DevStatLpfBw | DevStatSampleRate | DevStatFreq | DevStatVga;
BrfDevState s(DevStatAbortOnFail | DevStatLoopback,flags,flags);
s.m_tx.lpfBw = bw;
s.m_rx.lpfBw = bw;
s.m_tx.sampleRate = sampleRate;
s.m_rx.sampleRate = sampleRate;
s.m_tx.frequency = txFreq;
s.m_rx.frequency = rxFreq;
s.m_tx.vga1 = p.getIntValue(YSTRING("txvga1"),
BRF_TXVGA1_GAIN_DEF,BRF_TXVGA1_GAIN_MIN,BRF_TXVGA1_GAIN_MAX);
s.m_tx.vga2 = p.getIntValue(YSTRING("txvga2"),
BRF_TXVGA2_GAIN_DEF,BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX);
s.m_rx.vga1 = p.getIntValue(YSTRING("rxvga1"),
BRF_RXVGA1_GAIN_DEF,BRF_RXVGA1_GAIN_MIN,BRF_RXVGA1_GAIN_MAX);
s.m_rx.vga2 = p.getIntValue(YSTRING("rxvga2"),
BRF_RXVGA2_GAIN_DEF,BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX);
NamedList lpParams("");
lpParams.copySubParams(p,YSTRING("loopback_"));
int defLp = brfIsLowBand(txFreq) ? LoopRfLna1 : LoopRfLna2;
int lp = p.getIntValue(YSTRING("loopback"),s_loopback,defLp);
s.setLoopback(lp,lpParams);
// Stop I/O threads (use internal functions to set params)
BRF_FUNC_CALL_BREAK(calThreadsPause(true,&e));
BRF_FUNC_CALL_BREAK(setState(s,&e));
// Toggle timestamps (reset FPGA timestamps)
enableRfFpgaBoth(false);
enableTimestamps(false);
Thread::idle();
BRF_FUNC_CALL_BREAK(enableTimestamps(true,&e));
BRF_FUNC_CALL_BREAK(enableRfFpgaBoth(true,&e));
BRF_FUNC_CALL_BREAK(calThreadsPause(false,&e));
// Utility: check / write LMS
checkLMS(p[YSTRING("lms_check")],0,true);
BRF_FUNC_CALL_BREAK(writeLMS(p[YSTRING("lms_write")],&e,true));
Thread::msleep(50);
// Set read / test signal buffers. Generate tone
float omega = ((float)sampleRate / 4 - deltaFreq) * 2 * M_PI / sampleRate;
ComplexVector buf(getRxSamples(p));
ComplexVector testTone(buf.length());
omega = -omega;
generateExpTone(testTone,omega);
float limit = getSampleLimit(p);
ComplexVector testPattern;
const String& pattern = p[YSTRING("test_pattern")];
if (pattern) {
String ep;
if (!buildVector(ep,pattern,testPattern,buf.length(),true)) {
status = RadioInterface::Failure;
e << "invalid/unknown test_pattern='" << pattern << "' - " << ep;
break;
}
if (testPattern.length() > buf.length())
testPattern.resize(buf.length());
}
unsigned int trace = p.getIntValue(YSTRING("trace"),0,0);
bool dumpTxTs = (trace > 1) && p.getBoolValue("dump_tx_ts");
String t;
if (trace) {
if (p.getBoolValue("dump_status_start"))
dumpState(t,p,true);
String tmp;
unsigned int h = p.getIntValue("dump_test_tone",0,0);
if (h) {
if (h > testTone.length())
h = testTone.length();
tmp.printf("TEST TONE HEAD(%d):",h);
testTone.head(h).dump(tmp,Math::dumpComplex," ","(%g,%g)");
}
if (testPattern.length()) {
h = p.getIntValue("dump_test_pattern",0,0);
if (h) {
String t2;
t2.printf("TEST PATTERN len=%u HEAD(%d):",testPattern.length(),h);
if (h > testPattern.length())
h = testPattern.length();
testPattern.head(h).dump(t2,Math::dumpComplex," ","(%g,%g)");
tmp.append(t2,"\r\n");
}
}
t.append(tmp,"\r\n");
Output("Loopback check: frequency tx=%u rx=%u (delta=%u omega=%f) "
"samplerate=%u bandwidth=%u samples=%u buffers=%u [%p]%s",
txFreq,rxFreq,deltaFreq,omega,sampleRate,bw,buf.length(),
nBuffs,m_owner,encloseDashes(t,true));
}
else if (p.getBoolValue("dump_dev")) {
String t;
dumpState(t,p,true);
Debug(m_owner,DebugNote,"Loopback check. Device params: [%p]%s",this,encloseDashes(t));
}
// Dump header to file
if (dump.dumpHeader()) {
String* tmp = new String;
dumpState(*tmp,p,true,true);
String extra;
extra.printf("\r\nSAMPLES: %u\r\nBUFFERS: %u\r\nomega: %f\r\ndelta_freq=%u",
buf.length(),nBuffs,omega,deltaFreq);
*tmp << "\r\n" << extra << "\r\n";
dump.append(tmp);
}
// Run it
int dumpRxBeforeRead = p.getIntValue(YSTRING("dump_before_read_rx"),0,0);
int dumpTxBeforeRead = p.getIntValue(YSTRING("dump_before_read_tx"),0,0);
unsigned int tmp = nBuffs / 4;
unsigned int limitFailures =
p.getIntValue(YSTRING("sample_limit_allow_fail"),tmp,0,nBuffs - 1);
unsigned int allowFail = p.getIntValue(YSTRING("allow_fail"),tmp,0,nBuffs - 1);
for (int i = 0; i < (int)nBuffs; i++) {
if (dumpRxBeforeRead) {
dumpRxBeforeRead--;
showBuf(false,1,false);
}
if (dumpTxBeforeRead || dumpTxTs) {
if (dumpTxBeforeRead)
dumpTxBeforeRead--;
showBuf(true,1,dumpTxTs);
}
BRF_FUNC_CALL_BREAK(setStateSyncTx(0,&e));
uint64_t ts = m_syncTxState.m_tx.m_timestamp + m_radioCaps.rxLatency;
BRF_FUNC_CALL_BREAK(capture(false,(float*)buf.data(),buf.length(),ts,&e));
// Check for out of range values
status = checkSampleLimit((float*)buf.data(),buf.length(),limit,&e);
if (status) {
if (trace)
Output("%-5u [%10s]\tsample invalid (remains=%d): %s",
i,String(ts).c_str(),limitFailures,e.c_str());
if (!limitFailures)
break;
limitFailures--;
i--;
e.clear();
status = 0;
continue;
}
// Apply test pattern (check algorithm)
if (testPattern.length())
buf.copy(testPattern,testPattern.length());
// Calculate test / total signal
const Complex* last = 0;
const Complex* b = buf.data(0,buf.length(),last);
Complex testSum;
float total = 0;
for (const Complex* tt = testTone.data(); b != last; ++b, ++tt) {
total += b->norm2();
testSum += *tt * *b;
}
float test = testSum.norm2() / buf.length();
bool ok = ((0.5 * total) < test) && (test <= total);
float ratio = total ? test / total : -1;
if (trace > 1)
Output("%-5u [%10s]\ttest:%-15f total:%-15f %.2f %s",
i,String(ts).c_str(),test,total,ratio,(ok ? "" : "FAILURE"));
// Dump to file
if ((ok && dump.dumpOk()) || (!ok && dump.dumpFail())) {
String* tmp = new String;
tmp->printf("\r\n# %u [%s] test:%f total:%f\r\n",
i,(ok ? "SUCCESS" : "FAILURE"),test,total);
dump.append(tmp);
dump.appendFormatted(buf,ok);
}
if (ok)
continue;
e.printf("test_tone_power=%f total_energy=%f (%.2f)",test,total,ratio);
if (!allowFail) {
status = RadioInterface::Failure;
break;
}
allowFail--;
DDebug(m_owner,DebugInfo,"Loopback check failure %s [%p]",e.safe(),m_owner);
e.clear();
}
if (status)
break;
if (trace == 1)
Output("Loopback check succesfully ended");
// VGA test
BRF_FUNC_CALL_BREAK(testVgaCheck(p,"Loopback check",omega,&e));
break;
}
Debug(m_owner,DebugAll,"Finalizing loopback check [%p]",m_owner);
if (!status) {
calThreadsPause(true);
status = setState(oldState,&e);
calThreadsPause(false);
}
duration.stop();
if (status == 0) {
Debug(m_owner,DebugNote,"Loopback check ended duration=%s [%p]",
duration.secStr(),m_owner);
return 0;
}
e.printf(1024,"Loopback check failed: %s",e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::testVga(const char* loc, bool tx, bool preMixer,
float omega, String* error)
{
Configuration cfg;
loadCfg(&cfg,false);
NamedList& params = *cfg.createSection("test-vga");
String e;
unsigned int status = 0;
String what;
what << (tx ? "tx_vga" : "rx_vga") << mixer(preMixer);
String testName;
testName.printf("Test %s VGA %c",brfDir(tx),mixer(preMixer));
String fName = params.getValue("dump_file","test_${what}_${sec_now}");
replaceDumpParams(fName,new NamedString("what",what),true,
new NamedString("loopback",lookup(m_state.m_loopback,s_loopback)));
BrfDumpFile dump(&NamedList::empty(),fName,true);
if (!dump.valid()) {
int e = dump.file().error();
Debug(m_owner,DebugNote,"%s '%s' failed to create dump file '%s': %d [%p]",
testName.c_str(),loc,dump.fileName().c_str(),e,m_owner);
return 0;
}
int start = 0;
int end = 0;
uint8_t mask = 0;
uint8_t shift = 0;
#define BRF_TEST_INIT(from,to,ma,sh) { \
start = from; \
end = to; \
mask = ma; \
shift = sh; \
}
if (tx) {
if (preMixer)
BRF_TEST_INIT(BRF_TXVGA1_GAIN_MIN,BRF_TXVGA1_GAIN_MAX,0x1f,0)
else
BRF_TEST_INIT(BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX,0xf8,3)
}
else if (preMixer)
BRF_TEST_INIT(BRF_RXVGA1_GAIN_MIN,BRF_RXVGA1_GAIN_MAX,0x7f,0)
else
BRF_TEST_INIT(BRF_RXVGA2_GAIN_MIN,BRF_RXVGA2_GAIN_MAX,0x1f,0)
#undef BRF_TEST_INIT
unsigned int flags = preMixer ? DevStatVga1 : DevStatVga2;
unsigned int len = end - start + 1;
BrfDevState oldState(m_state,0,DevStatVga,DevStatVga);
FloatVector totalMed(len);
FloatVector totalMin(len);
FloatVector totalMax(len);
FloatVector totalDelta(len);
FloatVector testMed(len);
FloatVector testDelta(len);
FloatVector testMin(len);
FloatVector testMax(len);
FloatVector testTotalMed(len);
FloatVector testTotalDelta(len);
FloatVector testTotalMin(len);
FloatVector testTotalMax(len);
totalMin.fill(BRF_MAX_FLOAT);
totalMax.fill(-BRF_MAX_FLOAT);
testMin.fill(BRF_MAX_FLOAT);
testMax.fill(-BRF_MAX_FLOAT);
testTotalMin.fill(BRF_MAX_FLOAT);
testTotalMax.fill(-BRF_MAX_FLOAT);
ComplexVector buf(getRxSamples(params));
ComplexVector testTone(buf.length());
generateExpTone(testTone,omega);
const String& regFmt = params["dump_reg"];
uint8_t addr = 0;
DataBlock regVal;
if (regFmt) {
addr = lmsVgaAddr(tx,preMixer);
regVal.resize(len);
}
bool div = params.getBoolValue("divide_by_samples");
float limit = getSampleLimit(params);
unsigned int nBuffs = params.getIntValue("buffers",10,2);
// Set all VGA values to default
m_syncTxState.m_tx.vga1 = BRF_TXVGA1_GAIN_DEF;
m_syncTxState.m_tx.vga2 = BRF_TXVGA2_GAIN_DEF;
m_syncTxState.m_rx.vga1 = BRF_RXVGA1_GAIN_DEF;
m_syncTxState.m_rx.vga2 = BRF_RXVGA2_GAIN_DEF;
m_syncTxState.setFlags(0,DevStatVga,DevStatVga);
status = setStateSync(&e);
// Dump the header now to have current VGA values
const String& hdr = params["dump_header"];
if (hdr) {
NamedString* ns = new NamedString("data");
if (loc)
*ns << "\r\n" << loc;
String tmp;
tmp.printf("\r\n%s\r\nRange: [%d..%d] (%u)\r\n\r\n",
testName.c_str(),start,end,len);
*ns << tmp;
dumpState(*ns,params,true,true);
if (*ns)
*ns << "\r\n";
*ns <<
"\r\nSAMPLES: " << buf.length() <<
"\r\nBUFFERS: " << nBuffs;
dump.append(replaceDumpParamsFmt(hdr,ns));
}
String tmp;
BrfDevDirState& setSync = tx ? m_syncTxState.m_tx : m_syncTxState.m_rx;
int& set = preMixer ? setSync.vga1 : setSync.vga2;
for (unsigned int i = 0; !status && i < len; i++) {
set = start + i;
m_syncTxState.setFlags(0,tx ? flags : 0,tx ? 0 : flags);
BRF_FUNC_CALL_BREAK(setStateSync(&e));
Thread::msleep(100);
BRF_FUNC_CALL_BREAK(setStateSyncTx(0,&e));
uint64_t ts = m_syncTxState.m_tx.m_timestamp + m_radioCaps.rxLatency;
if (regVal.length())
readLMS(addr,*regVal.data(i),0,true);
for (unsigned int n = 0; n < nBuffs; n++) {
BRF_FUNC_CALL_BREAK(capture(false,(float*)buf.data(),buf.length(),ts,&e));
ts += buf.length();
// Check for out of range values
BRF_FUNC_CALL_BREAK(checkSampleLimit((float*)buf.data(),buf.length(),
limit,&e));
// Calculate test / total signal
const Complex* last = 0;
const Complex* b = buf.data(0,buf.length(),last);
Complex testSum;
float tmpTotal = 0;
for (const Complex* tt = testTone.data(); b != last; ++b, ++tt) {
tmpTotal += b->norm2();
testSum += *tt * *b;
}
float tmpTest = testSum.norm2() / buf.length();
if (div) {
tmpTotal /= buf.length();
tmpTest /= buf.length();
}
float t_t = 100 * (tmpTotal ? (tmpTest / tmpTotal) : 0);
setMinMax(totalMin[i],totalMax[i],tmpTotal);
setMinMax(testMin[i],testMax[i],tmpTest);
setMinMax(testTotalMin[i],testTotalMax[i],t_t);
totalMed[i] += tmpTotal;
testMed[i] += tmpTest;
testTotalMed[i] += t_t;
}
if (status)
break;
totalMed[i] /= nBuffs;
testMed[i] /= nBuffs;
testTotalMed[i] /= nBuffs;
if (totalMed[i])
totalDelta[i] = 100 * (totalMax[i] - totalMin[i]) / totalMed[i];
if (testMed[i])
testDelta[i] = 100 * (testMax[i] - testMin[i]) / testMed[i];
if (testTotalMed[i])
testTotalDelta[i] = 100 * (testTotalMax[i] - testTotalMin[i]) / testTotalMed[i];
}
m_syncTxState = oldState;
setStateSync();
Debug(m_owner,DebugInfo,"%s '%s' dumping to '%s' [%p]",
testName.c_str(),loc,dump.fileName().c_str(),m_owner);
String count(len);
if (regVal.length()) {
NamedString* a = new NamedString("address","0x" + tmp.hexify(&addr,1));
NamedString* reg_val = new NamedString("data");
NamedString* value = new NamedString("value");
for (unsigned int i = 0; i < regVal.length(); i++) {
uint8_t* d = regVal.data(i);
reg_val->append("0x" + tmp.hexify(d,1),",");
value->append(String((*d & mask) >> shift),",");
}
dump.append(replaceDumpParamsFmt(regFmt,a,false,reg_val,value));
}
#define BRF_CHECK_FMT_DUMP_VECT(param,v) { \
const String& fmt = params[param]; \
if (fmt) \
dump.appendFormatted(v,fmt); \
}
BRF_CHECK_FMT_DUMP_VECT("dump_total_med",totalMed);
BRF_CHECK_FMT_DUMP_VECT("dump_total_delta",totalDelta);
BRF_CHECK_FMT_DUMP_VECT("dump_test_med",testMed);
BRF_CHECK_FMT_DUMP_VECT("dump_test_delta",testDelta);
BRF_CHECK_FMT_DUMP_VECT("dump_test_total_med",testTotalMed);
BRF_CHECK_FMT_DUMP_VECT("dump_test_total_delta",testTotalDelta);
#undef BRF_CHECK_FMT_DUMP_VECT
const String& mm = params["dump_total_minmax"];
if (mm)
dump.append(replaceDumpParamsFmt(mm,new NamedString("count",count),
false,dumpNsData(totalMin,"total_min"),dumpNsData(totalMax,"total_max")));
const String& extra = params["dump_extra"];
if (extra)
dump.append(replaceDumpParamsFmt(extra,new NamedString("count",count)));
// Done dumping
if (!status)
return 0;
e.printf(2048,"%s '%s' failed - %s",loc,testName.c_str(),e.c_str());
return showError(status,e,0,error);
}
static inline void computeMinMax(int& minVal, int& maxVal, int val)
{
if (minVal > val)
minVal = val;
if (maxVal < val)
maxVal = val;
}
static inline void computeRxAdjustPeak(int& p, int val, uint64_t& peakTs, uint64_t& ts)
{
if (p >= val)
return;
p = val;
peakTs = ts;
}
// DC offsets compensation feedback using an exponential moving average
static inline int computeCorrection(int& rxDcAvg, int offs, int avg, int dcOffsMax)
{
rxDcAvg = avg + ((BRF_RX_DC_OFFSET_AVG_DAMPING - 1) * rxDcAvg /
BRF_RX_DC_OFFSET_AVG_DAMPING);
if ((rxDcAvg > dcOffsMax)) {
if (offs < BRF_RX_DC_OFFSET_MAX) {
offs++;
rxDcAvg = 0;
}
}
else if ((rxDcAvg < -dcOffsMax) && (offs > -BRF_RX_DC_OFFSET_MAX)) {
offs--;
rxDcAvg = 0;
}
return offs;
}
// Compute Rx avg values, autocorrect offsets if configured
void BrfLibUsbDevice::computeRx(uint64_t ts)
{
unsigned int dbg = checkDbgInt(m_rxShowDcInfo);
if (!(dbg || m_state.m_rxDcAuto))
return;
// Compute averages and peak values
int dcIMin = 32767;
int dcIMax = -32767;
int dcIAvg = 0;
int dcQMin = 32767;
int dcQMax = -32767;
int dcQAvg = 0;
int peak = 0;
uint64_t peakTs = 0;
for (unsigned int i = 0; i < m_rxIO.buffers; i++) {
int16_t* d = m_rxIO.samples(i);
for (int16_t* last = m_rxIO.samplesEOF(i); d != last;) {
int dcI = *d++;
int dcQ = *d++;
dcIAvg += dcI;
dcQAvg += dcQ;
if (!dbg)
continue;
computeMinMax(dcIMin,dcIMax,dcI);
computeMinMax(dcQMin,dcQMax,dcQ);
computeRxAdjustPeak(peak,dcIMax,peakTs,ts);
computeRxAdjustPeak(peak,-dcIMin,peakTs,ts);
computeRxAdjustPeak(peak,dcQMax,peakTs,ts);
computeRxAdjustPeak(peak,-dcQMin,peakTs,ts);
ts++;
}
}
int div = m_rxIO.buffers * m_rxIO.bufSamples;
dcIAvg /= div;
dcQAvg /= div;
if (dbg)
Debug(m_owner,DebugInfo,
"RX DC values min/avg/max I=%d/%d/%d Q=%d/%d/%d peak=%d TS=" FMT64U " [%p]",
dcIMin,dcIAvg,dcIMax,dcQMin,dcQAvg,dcQMax,peak,peakTs,m_owner);
if (!m_state.m_rxDcAuto)
return;
int corrI = computeCorrection(m_rxDcAvgI,m_state.m_rx.dcOffsetI,dcIAvg,m_rxDcOffsetMax);
int corrQ = computeCorrection(m_rxDcAvgQ,m_state.m_rx.dcOffsetQ,dcQAvg,m_rxDcOffsetMax);
if (corrI == m_state.m_rx.dcOffsetI && corrQ == m_state.m_rx.dcOffsetQ)
return;
BRF_TX_SERIALIZE_NONE;
DDebug(m_owner,DebugInfo,"Adjusting Rx DC offsets I=%d Q=%d [%p]",
corrI,corrQ,m_owner);
internalSetCorrectionIQ(false,corrI,corrQ);
}
// Set error string or put a debug message
unsigned int BrfLibUsbDevice::showError(unsigned int code, const char* error,
const char* prefix, String* buf, int level)
{
if (buf)
return setError(code,buf,error,prefix);
String tmp;
setError(code,&tmp,error,prefix);
switch (code) {
case RadioInterface::Pending:
case RadioInterface::Cancelled:
level = DebugAll;
break;
}
Debug(m_owner,level,"%s [%p]",tmp.c_str(),m_owner);
return code;
}
void BrfLibUsbDevice::printIOBuffer(bool tx, const char* loc, int index, unsigned int nBufs)
{
BrfDevIO& io = getIO(tx);
if (!nBufs)
nBufs = io.buffers;
for (unsigned int i = 0; i < nBufs; i++) {
if (index >= 0 && index != (int)i)
continue;
String s;
if (io.showBufData)
io.dumpInt16Samples(s,i);
Output("%s: %s [%s] buffer %u TS=" FMT64U " [%p]%s",
m_owner->debugName(),brfDir(tx),loc,i,io.bufTs(i),m_owner,
encloseDashes(s,true));
}
}
void BrfLibUsbDevice::dumpIOBuffer(BrfDevIO& io, unsigned int nBufs)
{
nBufs = checkDbgInt(io.dataDump,nBufs);
for (unsigned int i = 0; i < nBufs; i++)
if (!io.dataDumpFile.write(io.bufTs(i),io.samples(i),io.bufSamplesLen,owner()))
nBufs = 0;
if (!nBufs)
io.dataDumpFile.terminate(owner());
}
void BrfLibUsbDevice::updateIODump(BrfDevIO& io)
{
Lock lck(m_dbgMutex);
NamedList lst[2] = {io.dataDumpParams,io.upDumpParams};
io.dataDumpParams.assign("");
io.dataDumpParams.clearParams();
io.upDumpParams.assign("");
io.upDumpParams.clearParams();
lck.drop();
NamedList p(Engine::runParams());
p.addParam("boardserial",serial());
for (uint8_t i = 0; i < 2; i++) {
NamedList& nl = lst[i];
if (!nl)
continue;
RadioDataFile& f = i ? io.upDumpFile : io.dataDumpFile;
int& dump = i ? io.upDump : io.dataDump;
f.terminate(owner());
dump = 0;
int n = 0;
String file = nl[YSTRING("file")];
if (file) {
n = nl.getIntValue(YSTRING("count"),-1);
if (!n)
file = 0;
}
if (file)
p.replaceParams(file);
if (!file)
continue;
RadioDataDesc d;
if (!i) {
d.m_elementType = RadioDataDesc::Int16;
d.m_littleEndian = true;
}
if (f.open(file,&d,owner()))
dump = n;
}
}
// Enable or disable loopback
unsigned int BrfLibUsbDevice::internalSetLoopback(int mode, const NamedList& params,
String* error)
{
if (m_state.m_loopback == mode)
return 0;
const char* what = lookup(mode,s_loopback);
#if 1
XDebug(m_owner,DebugAll,"internalSetLoopback(%d) '%s' [%p]",mode,what,m_owner);
#else
Debugger d(DebugAll,"BrfLibUsbDevice::internalSetLoopback()"," %d '%s' [%p]",
mode,what,m_owner);
#endif
String e;
unsigned int status = 0;
BrfDevTmpAltSet tmpAltSet(this,status,&e,"Set loopback");
int lna = LmsLnaNone;
while (status == 0) {
// Disable everything before enabling the loopback
BRF_FUNC_CALL_BREAK(lnaSelect(LmsLnaNone,&e));
BRF_FUNC_CALL_BREAK(paSelect(LmsPaNone,&e));
BRF_FUNC_CALL_BREAK(setLoopbackPath(LoopNone,e));
// Prepare the loopback (enable / disable modules)
switch (mode) {
case LoopFirmware:
status = RadioInterface::OutOfRange;
e = "Not implemented";
break;
case LoopLpfToRxOut:
// Disable RX VGA2 and LPF
BRF_FUNC_CALL_BREAK(internalEnableRxVga(false,false,&e));
BRF_FUNC_CALL_BREAK(internalSetLpf(false,LpfDisabled,&e));
break;
case LoopLpfToVga2:
case LoopVga1ToVga2:
// Disable RX VGA1 and LPF
BRF_FUNC_CALL_BREAK(internalEnableRxVga(false,false,&e));
BRF_FUNC_CALL_BREAK(internalSetLpf(false,LpfDisabled,&e));
break;
case LoopLpfToLpf:
case LoopVga1ToLpf:
// Disable RX VGA1, Enable RX LPF and RX VGA2
BRF_FUNC_CALL_BREAK(internalEnableRxVga(false,true,&e));
BRF_FUNC_CALL_BREAK(internalSetLpf(false,LpfNormal,&e));
BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,false,&e));
break;
case LoopRfLna1:
lna = LmsLna1;
// falthrough
case LoopRfLna2:
if (lna == LmsLnaNone)
lna = LmsLna2;
// falthrough
case LoopRfLna3:
if (lna == LmsLnaNone)
lna = LmsLna3;
// Select PA AUX and enable LNA
BRF_FUNC_CALL_BREAK(paSelect(LmsPaAux,&e));
BRF_FUNC_CALL_BREAK(lnaEnable(true,&e));
BRF_FUNC_CALL_BREAK(lnaSelect(lna,&e));
// Enable RX path
BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,true,&e));
BRF_FUNC_CALL_BREAK(internalSetLpf(false,LpfNormal,&e));
BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,false,&e));
// Select output buffer in RX PLL
BRF_FUNC_CALL_BREAK(lmsSet(0x25,lna,0x03,&e));
break;
case LoopNone:
BRF_FUNC_CALL_BREAK(restoreFreq(true,&e));
BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,true,&e));
BRF_FUNC_CALL_BREAK(internalSetLpf(false,LpfNormal,&e));
BRF_FUNC_CALL_BREAK(internalEnableRxVga(true,false,&e));
BRF_FUNC_CALL_BREAK(lnaEnable(true,&e));
BRF_FUNC_CALL_BREAK(restoreFreq(false,&e));
lna = LmsLnaDetect;
break;
default:
Debug(m_owner,DebugStub,"Loopback: unhandled value %d [%p]",mode,m_owner);
status = setUnkValue(e,"mode " + String(mode));
}
if (status)
break;
BRF_FUNC_CALL_BREAK(setLoopbackPath(mode,e));
bool lowBand = brfIsLowBand(m_state.m_tx.frequency);
if (lna == LmsLnaDetect)
BRF_FUNC_CALL_BREAK(lnaSelect(lowBand ? LmsLna1 : LmsLna2,&e));
if (params.getBoolValue(YSTRING("transmit"),mode == LoopNone))
BRF_FUNC_CALL_BREAK(paSelect(lowBand,&e));
break;
}
if (status == 0) {
Debug(m_owner,DebugNote,"Loopback changed '%s' -> '%s' [%p]",
lookup(m_state.m_loopback,s_loopback),what,m_owner);
m_state.setLoopback(mode,params);
return 0;
}
if (mode != LoopNone)
e.printf(1024,"Failed to set loopback to %d (%s): %s",
mode,what,e.c_str());
else
e.printf(1024,"Failed to disable loopback: %s",e.c_str());
return showError(status,e,0,error);
}
unsigned int BrfLibUsbDevice::setLoopbackPath(int mode, String& error)
{
const char* what = lookup(mode,s_loopback);
XDebug(m_owner,DebugAll,"setLoopbackPath(%d) '%s' [%p]",mode,what,m_owner);
uint8_t rf = 0;
uint8_t baseband = 0;
String e;
unsigned int status = lmsRead2(0x08,rf,0x46,baseband,&e);
if (status == 0) {
// Stop all loopbacks
rf &= ~0x7f;
baseband &= ~0x0c;
switch (mode) {
case LoopFirmware:
status = RadioInterface::OutOfRange;
e = "Not implemented";
break;
case LoopLpfToRxOut:
rf |= 0x10; // LBEN_OPIN
baseband |= 0x04; // LOOPBBEN[1:0] 1
break;
case LoopLpfToVga2:
rf |= 0x20; // LBEN_VGA2IN
baseband |= 0x04; // LOOPBBEN[1:0] 1
break;
case LoopVga1ToVga2:
rf |= 0x20; // LBEN_VGA2IN
baseband |= 0x08; // LOOPBBEN[1:0] 2
break;
case LoopLpfToLpf:
rf |= 0x40; // LBEN_LPFIN
baseband |= 0x04; // LOOPBBEN[1:0] 1
break;
case LoopVga1ToLpf:
rf |= 0x40; // LBEN_LPFIN
baseband |= 0x08; // LOOPBBEN[1:0] 2
break;
case LoopRfLna1:
rf |= 0x01;
break;
case LoopRfLna2:
rf |= 0x02;
break;
case LoopRfLna3:
rf |= 0x03;
break;
case LoopNone:
break;
default:
Debug(m_owner,DebugStub,"Loopback path set: unhandled value %d [%p]",
mode,m_owner);
status = setUnkValue(e,"mode " + String(mode));
}
if (status == 0)
status = lmsWrite2(0x08,rf,0x46,baseband,&e);
}
if (status == 0)
Debug(m_owner,DebugAll,"Loopback path switches configured for '%s' [%p]",
what,m_owner);
else
error << "Failed to configure path switches - " << e;
return status;
}
void BrfLibUsbDevice::dumpLoopbackStatus(String* dest)
{
#define BRF_LS_RESULT(name,mask) \
s << "\r\n " << name << ": " << (status ? "ERROR" : tmp.printf("0x%x",data & mask).c_str())
#define BRF_LS_RESULT_OPEN(name,mask,valueOpen) \
BRF_LS_RESULT(name,mask); \
if (status == 0) \
s << " - " << (((data & mask) == valueOpen) ? "open" : "closed");
String s;
String tmp;
uint8_t data = 0;
unsigned int status = 0;
// TX Path
s << "\r\nTX PATH:";
status = lmsRead(0x35,data);
BRF_LS_RESULT("BYP_EN_LPF",0x40); // LPF bypass enable (1: bypass, 0: normal)
if (!status)
s << " - " << lookup((data & 0x40) == 0x40 ? LpfBypass : LpfNormal,s_lpf);
status = lmsRead(0x46,data);
BRF_LS_RESULT_OPEN("LOOPBBEN[1:0]",0x0c,0x00); // Baseband loopback swithes control (00: open, 11: closed)
status = lmsRead(0x08,data);
BRF_LS_RESULT_OPEN("LBEN_OPIN",0x10,0x00); // enabled: RX VGA2 and RXLPF should be disabled
BRF_LS_RESULT_OPEN("LBEN_VGA2IN",0x20,0x00); // enabled: LPF should be disabled
BRF_LS_RESULT_OPEN("LBEN_LPFIN",0x40,0x00); // enabled: RXTIA should be disabled
BRF_LS_RESULT("LBRFEN (TXMIX)",0x0f); // LNAs should be disabled
if (!status) {
s << " - ";
switch (data & 0x0f) {
case 0: s << "open"; break;
case 1: s << "LNA1"; break;
case 2: s << "LNA2"; break;
case 3: s << "LNA3"; break;
default: s << "invalid";
}
}
s << "\r\nRX PATH:";
status = lmsRead(0x55,data);
BRF_LS_RESULT("BYP_EN_LPF",0x40); // LPF bypass enable
if (!status)
s << " - " << lookup((data & 0x40) == 0x40 ? LpfBypass : LpfNormal,s_lpf);
status = lmsRead(0x09,data);
BRF_LS_RESULT_OPEN("RXOUTSW",0x80,0x00); // RXOUTSW switch
if (dest)
*dest = s;
else
Debug(m_owner,DebugAll,"Loopback switches: [%p]%s",m_owner,encloseDashes(s));
#undef BRF_LS_RESULT
#undef BRF_LS_RESULT_OPEN
}
void BrfLibUsbDevice::dumpLmsModulesStatus(String* dest)
{
#define BRF_MS_RESULT(name,result) \
s << "\r\n " << name << ": " << (status ? "ERROR" : result)
#define BRF_MS_RESULT_ACTIVE(name,result,mask,valueActive) \
BRF_MS_RESULT(name,result); \
if (status == 0) \
s << tmp.printf("0x%x - %s",data & mask,activeStr((data & mask) == valueActive))
String s;
String tmp;
int tmpInt = 0;
uint8_t data = 0;
unsigned int status = 0;
// TX
s << "\r\nTX:";
status = internalGetLpf(true,&tmpInt);
BRF_MS_RESULT("LPF",lookup(tmpInt,s_lpf));
status = lmsRead(0x44,data);
BRF_MS_RESULT("AUXPA",tmp.printf("0x%x - %s",data & 0x04,enableStr((data & 0x04) == 0)));
BRF_MS_RESULT("PA",tmp.printf("0x%x - %d",data & 0x18,((data & 0x18) >> 3)));
s << "\r\nRX:";
status = lmsRead(0x75,data);
tmp.clear();
if (status == 0)
tmp.printf("0x%x (%d)",data & 0x30,(data & 0x30) >> 4);
BRF_MS_RESULT("LNA","");
s << "Selected: " << tmp.safe("ERROR");
status = lmsRead(0x7d,data);
if (status == 0)
s << tmp.printf(" - (0x%x %s)",data & 0x01,activeStr((data & 0x01) == 0));
else
s << " - Active: ERROR";
BRF_MS_RESULT_ACTIVE("VGA1","",0x08,0);
status = internalGetLpf(false,&tmpInt);
BRF_MS_RESULT("LPF",lookup(tmpInt,s_lpf));
status = lmsRead(0x64,data);
BRF_MS_RESULT_ACTIVE("VGA2","",0x02,0x02);
if (dest)
*dest = s;
else
Debug(m_owner,DebugAll,"LMS modules status: [%p]%s",m_owner,encloseDashes(s));
#undef BRF_MS_RESULT_ACTIVE
#undef BRF_MS_RESULT
}
unsigned int BrfLibUsbDevice::internalDumpPeripheral(uint8_t dev, uint8_t addr,
uint8_t len, String* buf, uint8_t lineLen)
{
uint8_t data[256];
unsigned int status = 0;
BRF_FUNC_CALL_RET(accessPeripheral(dev,false,addr,data,0,len));
bool outHere = (buf == 0);
String s;
if (!buf)
buf = &s;
if (lineLen) {
String s1, s2;
uint8_t* d = data;
uint8_t a = addr;
uint8_t n = len / lineLen;
for (; n; n--, d += lineLen, a += lineLen)
*buf << "\r\n" << s1.hexify(&a,1) << "\t" << s2.hexify(d,lineLen,' ');
n = len % lineLen;
if (n)
*buf << "\r\n" << s1.hexify(&a,1) << "\t" << s2.hexify(d,n,' ');
}
else
buf->hexify(data,len,' ');
if (outHere)
Output("%s %s status (addr=0x%x):%s",
m_owner->debugName(),s_uartDev[dev].c_str(),addr,encloseDashes(*buf));
return 0;
}
unsigned int BrfLibUsbDevice::startCalibrateThreads(String* error, const NamedList& params)
{
static String s_s[3] = {"recv_data", "send_data", "calibrate"};
static const char* s_n[3] = {"BrfDevRecv", "BrfDevSend", "BrfDevCalibrate"};
static int s_t[3] = {BrfThread::DevRecv, BrfThread::DevSend, BrfThread::DevCalibrate};
static Thread::Priority s_prio[3] = {Thread::High, Thread::High, Thread::Normal};
stopThreads();
BrfThread** threads[3] = {&m_recvThread,&m_sendThread,&m_calThread};
int i = 0;
for (; i < 3; i++) {
BrfThread*& th = *threads[i];
const char* prioStr = params.getValue(s_s[i] + "_priority");
Thread::Priority prio = Thread::priority(prioStr,s_prio[i]);
th = new BrfThread(this,s_t[i],params,s_n[i],prio);
th = th->start();
if (!th)
break;
}
if (i >= 3)
return 0;
stopThreads();
String e;
e << "Failed to start " << s_s[i] << " worker thread";
return showError(RadioInterface::Failure,e,0,error);
}
unsigned int BrfLibUsbDevice::calThreadsPause(bool on, String* error)
{
unsigned int status = 0;
if (on) {
status = BrfThread::pause(m_sendThread,&m_threadMutex,error);
if (!status)
status = BrfThread::pause(m_recvThread,&m_threadMutex,error);
}
else {
status = BrfThread::resume(m_recvThread,&m_threadMutex,error);
if (!status)
status = BrfThread::resume(m_sendThread,&m_threadMutex,error);
}
return status;
}
// Stop internal threads
void BrfLibUsbDevice::stopThreads()
{
// Soft cancel
BrfThread::cancelThread(m_calThread,&m_threadMutex,0,m_owner,m_owner);
// Wait for a while (avoid calibrate failure due to I/O thread termination)
if (m_calThread)
Thread::msleep(20);
BrfThread::cancelThread(m_sendThread,&m_threadMutex,0,m_owner,m_owner);
BrfThread::cancelThread(m_recvThread,&m_threadMutex,0,m_owner,m_owner);
// Hard cancel
BrfThread::cancelThread(m_calThread,&m_threadMutex,1000,m_owner,m_owner);
BrfThread::cancelThread(m_sendThread,&m_threadMutex,1000,m_owner,m_owner);
BrfThread::cancelThread(m_recvThread,&m_threadMutex,1000,m_owner,m_owner);
m_internalIoSemaphore.unlock();
m_internalIoTimestamp = 0;
}
unsigned int BrfLibUsbDevice::waitCancel(const char* loc, const char* reason,
String* error)
{
Debug(m_owner,DebugCrit,"%s: %s. Waiting for cancel... [%p]",loc,reason,m_owner);
unsigned int status = 0;
while (!status && !m_calibrateStop) {
Thread::idle();
status = cancelled(error);
}
return status;
}
// Apply parameters from start notification message
unsigned int BrfLibUsbDevice::applyStartParams(const NamedList& params, String* error)
{
const NamedString* fOffs = 0;
const NamedString* dc[] = {0,0};
const NamedString* fpga[] = {0,0};
bool haveParams = false;
for (const ObjList* o = params.paramList()->skipNull(); o; o = o->skipNext()) {
const NamedString* ns = static_cast<const NamedString*>(o->get());
if (ns->name() == YSTRING("RadioFrequencyOffset"))
fOffs = ns;
else if (ns->name() == YSTRING("tx_dc_i"))
dc[0] = ns;
else if (ns->name() == YSTRING("tx_dc_q"))
dc[1] = ns;
else if (ns->name() == YSTRING("tx_fpga_corr_phase"))
fpga[0] = ns;
else if (ns->name() == YSTRING("tx_fpga_corr_gain"))
fpga[1] = ns;
else
continue;
haveParams = true;
}
if (!haveParams)
return 0;
unsigned int status = 0;
if (fOffs) {
float f = fOffs->toDouble(m_freqOffset);
f = clampFloat(f,BRF_FREQ_OFFS_MIN,BRF_FREQ_OFFS_MAX,fOffs->name());
BRF_FUNC_CALL_RET(internalSetFreqOffs(f,0,error));
}
if (dc[0] && dc[1]) {
for (int i = 0; i < 2; i++) {
int val = dc[i]->toInteger();
val = clampInt(val,BRF_TX_DC_OFFSET_MIN,BRF_TX_DC_OFFSET_MAX,dc[i]->name());
BRF_FUNC_CALL_RET(internalSetDcOffset(true,i == 0,val,error));
}
}
else if (dc[0] || dc[1])
Debug(m_owner,DebugConf,"Initialize. Ignoring %s: tx_dc_%c is missing [%p]",
(dc[0] ? dc[0] : dc[1])->name().c_str(),(dc[0] ? 'q' : 'i'),m_owner);
if (fpga[0] && fpga[1]) {
for (int i = 0; i < 2; i++) {
int val = fpga[i]->toInteger();
val = clampInt(val,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX,fpga[i]->name());
BRF_FUNC_CALL_RET(internalSetFpgaCorr(true,i ? CorrFpgaGain : CorrFpgaPhase,val,error));
}
}
else if (fpga[0] || fpga[1])
Debug(m_owner,DebugConf,"Initialize. Ignoring %s: tx_fpga_corr_%s is missing [%p]",
(fpga[0] ? fpga[0] : fpga[1])->name().c_str(),(fpga[0] ? "gain" : "phase"),m_owner);
return 0;
}
//
// BrfInterface
//
BrfInterface::BrfInterface(const char* name)
: RadioInterface(name),
m_dev(0)
{
debugChain(&__plugin);
Debug(this,DebugAll,"Interface created [%p]",this);
}
BrfInterface::~BrfInterface()
{
Debug(this,DebugAll,"Interface destroyed [%p]",this);
}
void BrfInterface::notifyError(unsigned int status, const char* str, const char* oper)
{
if (!status)
return;
Message* m = new Message("module.update",0,true);
m->addParam("module",__plugin.name());
m->addParam("status","failure");
m->addParam("operation",oper,false);
completeDevInfo(*m);
setError(*m,status,str);
Engine::enqueue(m);
}
unsigned int BrfInterface::init(const NamedList& params, String& error)
{
if (m_dev)
return 0;
unsigned int status = 0;
if (!s_usbContextInit) {
Lock lck(__plugin);
if (!s_usbContextInit) {
status = BrfLibUsbDevice::lusbCheckSuccess(::libusb_init(0),&error,"libusb init failed");
if (!status) {
Debug(&__plugin,DebugAll,"Initialized libusb context");
s_usbContextInit = true;
lusbSetDebugLevel();
}
else
Debug(this,DebugNote,"Failed to create device: %s [%p]",error.c_str(),this);
}
}
if (!status) {
m_dev = new BrfLibUsbDevice(this);
m_radioCaps = &m_dev->capabilities();
Debug(this,DebugAll,"Created device (%p) [%p]",m_dev,this);
status = m_dev->open(params,error);
}
return status;
}
unsigned int BrfInterface::initialize(const NamedList& params)
{
return m_dev->initialize(params);
}
unsigned int BrfInterface::setParams(NamedList& params, bool shareFate)
{
unsigned int code = 0;
NamedList failed("");
#define SETPARAMS_HANDLE_CODE(c) { \
if (c) { \
if (!code || code == Pending) \
code = c; \
failed.addParam(cmd + "_failed",String(c)); \
if (shareFate && c != Pending) \
break; \
} \
}
#define METH_CALL(func) { \
unsigned int c = func(); \
SETPARAMS_HANDLE_CODE(c); \
continue; \
}
#define METH_CALL_1(func,value) { \
unsigned int c = func(value); \
SETPARAMS_HANDLE_CODE(c); \
continue; \
}
#define METH_CALL_2(func,value1,value2) { \
unsigned int c = func(value1,value2); \
SETPARAMS_HANDLE_CODE(c); \
continue; \
}
#ifdef XDEBUG
String tmp;
params.dump(tmp,"\r\n");
Debug(this,DebugAll,"setParams [%p]%s",this,encloseDashes(tmp,true));
#endif
for (ObjList* o = params.paramList()->skipNull(); o; o = o->skipNext()) {
NamedString* ns = static_cast<NamedString*>(o->get());
if (!ns->name().startsWith("cmd:"))
continue;
String cmd = ns->name().substr(4);
if (!cmd)
continue;
if (cmd == YSTRING("setSampleRate"))
METH_CALL_1(setSampleRate,(uint64_t)ns->toInt64());
if (cmd == YSTRING("setFilter"))
METH_CALL_1(setFilter,(uint64_t)ns->toInt64());
if (cmd == YSTRING("setTxFrequency"))
METH_CALL_2(setFrequency,(uint64_t)ns->toInt64(),true);
if (cmd == YSTRING("setRxFrequency"))
METH_CALL_2(setFrequency,(uint64_t)ns->toInt64(),false);
if (cmd == "calibrate")
METH_CALL(calibrate);
if (cmd.startsWith("devparam:")) {
unsigned int c = NotInitialized;
if (m_dev) {
NamedList p("");
p.copySubParams(params,cmd + "_");
c = m_dev->setParam(cmd.substr(9),*ns,p);
}
SETPARAMS_HANDLE_CODE(c);
continue;
}
Debug(this,DebugNote,"setParams: unhandled cmd '%s' [%p]",cmd.c_str(),this);
SETPARAMS_HANDLE_CODE(NotSupported);
}
#undef SETPARAMS_HANDLE_CODE
#undef METH_CALL
#undef METH_CALL_1
if (code)
params.copyParams(failed);
#ifdef XDEBUG
tmp.clear();
params.dump(tmp,"\r\n");
Debug(this,DebugAll,"setParams [%p]%s",this,encloseDashes(tmp,true));
#endif
return code;
}
unsigned int BrfInterface::send(uint64_t when, float* samples, unsigned size,
float* powerScale)
{
#ifdef XDEBUG
if (size) {
float sum = 0.0F;
for (unsigned i=0; i<2*size; i++)
sum += samples[i]*samples[i];
float dB = 10.0F*log10f(sum/size);
float scaleDB = 0.0F;
if (powerScale) scaleDB = 20.0F*log10f(*powerScale);
XDebug(this,DebugAll,"Sending at time " FMT64U " power %f dB to be scaled %f dB [%p]",
when, dB, scaleDB, this);
}
#endif
return m_dev->syncTx(when,samples,size,powerScale);
}
unsigned int BrfInterface::recv(uint64_t& when, float* samples, unsigned int& size)
{
return m_dev->syncRx(when,samples,size);
}
unsigned int BrfInterface::setFrequency(uint64_t hz, bool tx)
{
XDebug(this,DebugAll,"BrfInterface::setFrequency(" FMT64U ",%s) [%p]",
hz,brfDir(tx),this);
unsigned int status = m_dev->setFrequency(hz,tx);
if (status)
return status;
uint32_t tmp = 0;
status = m_dev->getFrequency(tmp,tx);
if (status)
return status;
uint32_t freq = (uint32_t)hz;
if (tmp == freq)
return 0;
int delta = tmp - freq;
Debug(this,DebugNote,"Set %s frequency requested=%u read=%u delta=%d [%p]",
brfDir(tx),freq,tmp,delta,this);
return NotExact;
}
unsigned int BrfInterface::getFrequency(uint64_t& hz, bool tx) const
{
uint32_t freq = 0;
unsigned int status = m_dev->getFrequency(freq,tx);
if (status == 0)
hz = freq;
return status;
}
unsigned int BrfInterface::setSampleRate(uint64_t hz)
{
XDebug(this,DebugAll,"BrfInterface::setSampleRate(" FMT64U ") [%p]",hz,this);
uint32_t srate = (uint32_t)hz;
unsigned int status = m_dev->setSamplerate(srate,true);
if (status)
return status;
status = m_dev->setSamplerate(srate,false);
if (status)
return status;
uint32_t tmp = 0;
status = m_dev->getSamplerate(tmp,true);
if (status)
return status;
if (tmp != srate) {
Debug(this,DebugNote,"Failed to set TX samplerate requested=%u read=%u [%p]",
srate,tmp,this);
return NotExact;
}
status = m_dev->getSamplerate(tmp,false);
if (status)
return status;
if (tmp != srate) {
Debug(this,DebugNote,"Failed to set RX samplerate requested=%u read=%u [%p]",
srate,tmp,this);
return NotExact;
}
return NoError;
}
unsigned int BrfInterface::getSampleRate(uint64_t& hz) const
{
uint32_t srate = 0;
unsigned int status = m_dev->getSamplerate(srate,true);
if (status == 0)
hz = srate;
return status;
}
unsigned int BrfInterface::setFilter(uint64_t hz)
{
XDebug(this,DebugAll,"BrfInterface::setFilter(" FMT64U ") [%p]",hz,this);
if (hz > 0xffffffff) {
Debug(this,DebugNote,
"Failed to set filter " FMT64U ": out of range [%p]",
hz,this);
return OutOfRange;
}
uint32_t band = hz;
unsigned int status = m_dev->setLpfBandwidth(band,true);
if (status)
return status;
status = m_dev->setLpfBandwidth(band,false);
if (status)
return status;
uint32_t tmp = 0;
status = m_dev->getLpfBandwidth(tmp,true);
if (status)
return status;
if (tmp != band){
Debug(this,DebugNote,"Failed to set TX filter band requested=%u read=%u [%p]",
band,tmp,this);
return NotExact;
}
status = m_dev->getLpfBandwidth(tmp,false);
if (status)
return status;
if (tmp != band){
Debug(this,DebugNote,"Failed to set RX filter band requested=%u read=%u [%p]",
band,tmp,this);
return NotExact;
}
return NoError;
}
unsigned int BrfInterface::getFilterWidth(uint64_t& hz) const
{
uint32_t band = 0;
unsigned int status = m_dev->getLpfBandwidth(band,true);
if (status == 0)
hz = band;
return status;
}
unsigned int BrfInterface::setRxGain(int val, unsigned port, bool preMixer)
{
XDebug(this,DebugAll,"BrfInterface::setRxGain(%d,%u,VGA%c) [%p]",
val,port,mixer(preMixer),this);
if (!m_dev->validPort(port))
return InvalidPort;
unsigned int status = m_dev->enableRxVga(true,preMixer);
if (status)
return status;
return m_dev->setRxVga(val,preMixer);
}
unsigned int BrfInterface::setTxGain(int val, unsigned port, bool preMixer)
{
XDebug(this,DebugAll,"BrfInterface::setTxGain(%d,%u,VGA%c) [%p]",
val,port,mixer(preMixer),this);
if (!m_dev->validPort(port))
return InvalidPort;
unsigned int status = m_dev->setTxVga(val,preMixer);
if (status)
return status;
int tmp = 0;
status = m_dev->getTxVga(tmp,preMixer);
if (status)
return status;
if (tmp == val)
return NoError;
Debug(this,DebugNote,"Failed to set TX VGA%c requested=%d read=%d [%p]",
mixer(preMixer),val,tmp,this);
return NotExact;
}
void BrfInterface::completeDevInfo(NamedList& p, bool full, bool retData)
{
RadioInterface::completeDevInfo(p,full,retData);
if (full && m_dev) {
p.addParam("address",m_dev->address(),false);
p.addParam("speed",String(m_dev->speedStr()).toLower());
p.addParam("serial",m_dev->serial(),false);
}
}
// Calibration. Automatic tx/rx gain setting
// Set pre and post mixer value
unsigned int BrfInterface::setGain(bool tx, int val, unsigned int port,
int* newVal) const
{
if (!m_dev->validPort(port))
return InvalidPort;
return m_dev->setGain(tx,val,newVal);
}
void BrfInterface::destroyed()
{
Debug(this,DebugAll,"Destroying device=(%p) [%p]",m_dev,this);
Lock lck(__plugin);
__plugin.m_ifaces.remove(this,false);
lck.drop();
TelEngine::destruct(m_dev);
RadioInterface::destroyed();
}
//
// BrfModule
//
BrfModule::BrfModule()
: Module("bladerf","misc",true),
m_ifaceId(0),
m_disciplineBusy(false),
m_lastDiscipline(0)
{
String tmp;
#ifdef HAVE_LIBUSB_VER
const libusb_version* ver = ::libusb_get_version();
tmp.printf(" using libusb %u.%u.%u.%u",ver->major,ver->minor,ver->micro,ver->nano);
if (!TelEngine::null(ver->rc))
tmp << " rc='" << ver->rc << "'";
if (!TelEngine::null(ver->describe))
tmp << " desc='" << ver->describe << "'";
#else
tmp = " using old libusb 1.0";
#endif
Output("Loaded module BladeRF%s",tmp.safe());
}
BrfModule::~BrfModule()
{
Output("Unloading module BladeRF");
if (m_ifaces.skipNull())
Debug(this,DebugWarn,"Exiting with %u interface(s) in list!!!",m_ifaces.count());
else if (s_usbContextInit) {
::libusb_exit(0);
Debug(this,DebugAll,"Cleared libusb context");
}
}
bool BrfModule::findIfaceByDevice(RefPointer<BrfInterface>& iface, void* dev)
{
if (!dev)
return false;
Lock lck(this);
for (ObjList* o = m_ifaces.skipNull(); o; o = o->skipNext()) {
iface = static_cast<BrfInterface*>(o->get());
if (iface && iface->isDevice(dev))
return true;
iface = 0;
}
return false;
}
void BrfModule::initialize()
{
Output("Initializing module BladeRF");
lock();
loadCfg();
NamedList gen(*s_cfg.createSection(YSTRING("general")));
NamedList lusb(*s_cfg.createSection(YSTRING("libusb")));
unlock();
if (!relayInstalled(RadioCreate)) {
setup();
installRelay(Timer);
installRelay(Halt);
installRelay(Control);
installRelay(RadioCreate,"radio.create",gen.getIntValue("priority",90));
}
lusbSetDebugLevel();
s_lusbSyncTransferTout = lusb.getIntValue(YSTRING("sync_transfer_timeout"),
LUSB_SYNC_TIMEOUT,20,500);
s_lusbCtrlTransferTout = lusb.getIntValue(YSTRING("ctrl_transfer_timeout"),
LUSB_CTRL_TIMEOUT,200,2000);
s_lusbBulkTransferTout = lusb.getIntValue(YSTRING("bulk_transfer_timeout"),
LUSB_BULK_TIMEOUT,200,2000);
setDebugPeripheral(gen);
setSampleEnergize(gen[YSTRING("sampleenergize")]);
// Reload interfaces
lock();
if (m_ifaces.skipNull()) {
ListIterator iter(m_ifaces);
for (GenObject* gen = 0; (gen = iter.get()) != 0; ) {
RefPointer<BrfInterface> iface = static_cast<BrfInterface*>(gen);
if (!iface)
continue;
unlock();
iface->reLoad();
iface = 0;
lock();
}
}
unlock();
}
bool BrfModule::received(Message& msg, int id)
{
if (id == RadioCreate) {
if (Engine::exiting())
return false;
// Override parameters from received params
const String& what = msg[YSTRING("radio_driver")];
if (what && what != YSTRING("bladerf"))
return false;
return createIface(msg);
}
if (id == Control) {
const String& comp = msg[YSTRING("component")];
RefPointer<BrfInterface> ifc;
if (comp == name() || findIface(ifc,comp))
return onCmdControl(ifc,msg);
return false;
}
if (id == Timer && msg.msgTime().sec() > m_lastDiscipline + 4) {
m_lastDiscipline = msg.msgTime().sec();
// protect BrfVctcxoDiscipliner objects from multiple threads
lock();
if (!m_disciplineBusy) {
m_disciplineBusy = true;
ListIterator iter(m_ifaces);
for (GenObject* gen = iter.get(); gen != 0; gen = iter.get()) {
RefPointer<BrfInterface> ifc = static_cast<BrfInterface*>(gen);
if (!ifc)
continue;
unlock();
BrfLibUsbDevice* dev = ifc->device();
// discipline VCTCXO according to BrfLibUsbDevice's local settings
if (dev)
dev->trimVctcxo(msg.msgTime().usec());
ifc = 0;
lock();
}
m_disciplineBusy = false;
}
unlock();
}
return Module::received(msg,id);
}
void BrfModule::statusModule(String& str)
{
Module::statusModule(str);
}
void BrfModule::statusParams(String& str)
{
Module::statusParams(str);
Lock lck(this);
str.append("ifaces=",",") << m_ifaces.count();
}
void BrfModule::statusDetail(String& str)
{
Module::statusDetail(str);
}
bool BrfModule::commandComplete(Message& msg, const String& partLine,
const String& partWord)
{
if (partLine == YSTRING("control")) {
itemComplete(msg.retValue(),name(),partWord);
completeIfaces(msg.retValue(),partWord);
return false;
}
String tmp = partLine;
if (tmp.startSkip("control")) {
if (tmp == name())
return completeStrList(msg.retValue(),partWord,s_modCmds);
RefPointer<BrfInterface> ifc;
if (findIface(ifc,tmp))
return completeStrList(msg.retValue(),partWord,s_ifcCmds);
}
return Module::commandComplete(msg,partLine,partWord);
}
bool BrfModule::createIface(NamedList& params)
{
// Debugger d(debugLevel(),"BrfModule::createIface()");
Lock lck(this);
NamedList p(*s_cfg.createSection("general"));
// Allow using a different interface profile
// Override general parameters
const String& profile = params[YSTRING("profile")];
if (profile && profile != YSTRING("general")) {
NamedList* sect = s_cfg.getSection(profile);
if (sect)
p.copyParams(*sect);
}
// Override parameters from received params
String prefix = params.getValue(YSTRING("radio_params_prefix"),"radio.");
if (prefix)
p.copySubParams(params,prefix,true,true);
BrfInterface* ifc = new BrfInterface(name() + "/" + String(++m_ifaceId));
lck.drop();
String error;
unsigned int status = ifc->init(p,error);
if (!status) {
ifc->completeDevInfo(params,true,true);
Lock lck(this);
m_ifaces.append(ifc)->setDelete(false);
return true;
}
ifc->setError(params,status,error);
ifc->notifyError(status,error,"create");
TelEngine::destruct(ifc);
return false;
}
void BrfModule::completeIfaces(String& dest, const String& partWord)
{
Lock lck(this);
for (ObjList* o = m_ifaces.skipNull(); o; o = o->skipNext()) {
RefPointer<BrfInterface> ifc = static_cast<BrfInterface*>(o->get());
if (ifc)
itemComplete(dest,ifc->toString(),partWord);
}
}
bool BrfModule::onCmdControl(BrfInterface* ifc, Message& msg)
{
static const char* s_help =
// FIXME:
"\r\ncontrol ifc_name txgain1 [value=]"
"\r\n Set or retrieve TX VGA 1 mixer gain"
"\r\ncontrol ifc_name txgain2 [value=]"
"\r\n Set or retrieve TX VGA 2 mixer gain"
"\r\ncontrol ifc_name rxgain1 [value=]"
"\r\n Set or retrieve RX VGA 1 mixer gain"
"\r\ncontrol ifc_name rxgain2 [value=]"
"\r\n Set or retrieve RX VGA 2 mixer gain"
"\r\ncontrol ifc_name txdci [value=]"
"\r\n Set or retrieve TX DC I correction"
"\r\ncontrol ifc_name txdcq [value=]"
"\r\n Set or retrieve TX DC Q correction"
"\r\ncontrol ifc_name txfpgaphase [value=]"
"\r\n Set or retrieve TX FPGA PHASE correction"
"\r\ncontrol ifc_name txfpgagain [value=]"
"\r\n Set or retrieve TX FPGA GAIN correction"
"\r\ncontrol ifc_name rxdci [value=]"
"\r\n Set or retrieve RX DC I correction"
"\r\ncontrol ifc_name rxdcq [value=]"
"\r\n Set or retrieve RX DC Q correction"
"\r\ncontrol ifc_name rxfpgaphase [value=]"
"\r\n Set or retrieve RX FPGA PHASE correction"
"\r\ncontrol ifc_name rxfpgagain [value=]"
"\r\n Set or retrieve RX FPGA GAIN correction"
"\r\ncontrol ifc_name balance value="
"\r\n Set software IQ gain balance"
"\r\ncontrol ifc_name gainexp bp= max="
"\r\n Set amp gain expansion breakpoint (dB) and +3 dB expansion (dB)"
"\r\ncontrol ifc_name phaseexp bp= max="
"\r\n Set amp phase expansion breakpoint (dB) and +3 dB expansion (deg)"
"\r\ncontrol ifc_name showstatus"
"\r\n Output interface status"
"\r\ncontrol ifc_name showboardstatus"
"\r\n Output board status"
"\r\ncontrol ifc_name showstatistics"
"\r\n Output interface statistics"
"\r\ncontrol ifc_name showtimestamps"
"\r\n Output interface and board timestamps"
"\r\ncontrol ifc_name showlms [addr=] [len=]"
"\r\n Output LMS registers"
"\r\ncontrol ifc_name lmswrite addr= value= [resetmask=]"
"\r\n Set LMS value at given address. Use reset mask for partial register set"
"\r\ncontrol ifc_name bufoutput tx=boolean [count=] [nodata=boolean]"
"\r\n Set TX/RX buffer output"
"\r\ncontrol ifc_name rxdcoutput [count=]"
"\r\n Set interface RX DC info output"
"\r\ncontrol ifc_name txpattern [pattern=]"
"\r\n Set interface TX pattern"
"\r\ncontrol ifc_name vgagain tx=boolean vga={1|2} [gain=]"
"\r\n Set or retrieve TX/RX VGA mixer gain"
"\r\ncontrol ifc_name correction tx=boolean corr={dc-i|dc-q|fpga-gain|fpga-phase} [value=]"
"\r\n Set or retrieve TX/RX DC I/Q or FPGA GAIN/PHASE correction"
"\r\ncontrol ifc_name freqoffs [{value= [stop=YES|no]}|drift=]"
"\r\n Set (absolute value or a drift expressed in ppb to force a clock trim) or retrieve the frequency offset"
"\r\ncontrol ifc_name show [info=status|statistics|timestamps|boardstatus|peripheral|freqcal] [peripheral=all|list(lms,gpio,vctcxo,si5338)] [addr=] [len=]"
"\r\n Verbose output various interface info"
"\r\ncontrol ifc_name freqcalstart [system_accuracy=] [count=]"
"\r\n Start or re-configure the frequency calibration process"
"\r\ncontrol ifc_name freqcalstop"
"\r\n Stop the frequency calibration process";
const String& cmd = msg[YSTRING("operation")];
// Module commands
if (!ifc) {
if (cmd == YSTRING("help")) {
msg.retValue() << s_help;
return true;
}
return false;
}
// Interface commands
if (cmd == YSTRING("txgain1"))
return onCmdGain(ifc,msg,1,true);
if (cmd == YSTRING("txgain2"))
return onCmdGain(ifc,msg,1,false);
if (cmd == YSTRING("rxgain1"))
return onCmdGain(ifc,msg,0,true);
if (cmd == YSTRING("rxgain2"))
return onCmdGain(ifc,msg,0,false);
if (cmd == YSTRING("vgagain"))
return onCmdGain(ifc,msg);
if (cmd == YSTRING("txdci"))
return onCmdCorrection(ifc,msg,1,BrfLibUsbDevice::CorrLmsI);
if (cmd == YSTRING("txdcq"))
return onCmdCorrection(ifc,msg,1,BrfLibUsbDevice::CorrLmsQ);
if (cmd == YSTRING("txfpgaphase"))
return onCmdCorrection(ifc,msg,1,BrfLibUsbDevice::CorrFpgaPhase);
if (cmd == YSTRING("txfpgagain"))
return onCmdCorrection(ifc,msg,1,BrfLibUsbDevice::CorrFpgaGain);
if (cmd == YSTRING("rxdci"))
return onCmdCorrection(ifc,msg,0,BrfLibUsbDevice::CorrLmsI);
if (cmd == YSTRING("rxdcq"))
return onCmdCorrection(ifc,msg,0,BrfLibUsbDevice::CorrLmsQ);
if (cmd == YSTRING("rxfpgaphase"))
return onCmdCorrection(ifc,msg,0,BrfLibUsbDevice::CorrFpgaPhase);
if (cmd == YSTRING("rxfpgagain"))
return onCmdCorrection(ifc,msg,0,BrfLibUsbDevice::CorrFpgaGain);
if (cmd == YSTRING("correction"))
return onCmdCorrection(ifc,msg);
if (cmd == YSTRING("lmswrite"))
return onCmdLmsWrite(ifc,msg);
if (cmd == YSTRING("bufoutput"))
return onCmdBufOutput(ifc,msg);
if (cmd == YSTRING("rxdcoutput")) {
if (!ifc->device())
return retMsgError(msg,"No device");
ifc->device()->showRxDCInfo(msg.getIntValue(YSTRING("count")));
return true;
}
if (cmd == YSTRING("txpattern")) {
if (!ifc->device())
return retMsgError(msg,"No device");
ifc->device()->setTxPattern(msg[YSTRING("pattern")]);
return true;
}
if (cmd == YSTRING("balance")) {
if (!ifc->device())
return retMsgError(msg,"No device");
const String txPB = msg[YSTRING("value")];
const float val = txPB.toDouble(1);
ifc->device()->setTxIQBalance(val);
return true;
}
if (cmd == YSTRING("gainexp")) {
if (!ifc->device())
return retMsgError(msg,"No device");
const String bp = msg[YSTRING("bp")];
const String max = msg[YSTRING("max")];
ifc->device()->setGainExp(bp.toDouble(1),max.toDouble(1));
return true;
}
if (cmd == YSTRING("phaseexp")) {
if (!ifc->device())
return retMsgError(msg,"No device");
const String bp = msg[YSTRING("bp")];
const String max = msg[YSTRING("max")];
ifc->device()->setPhaseExp(bp.toDouble(1),max.toDouble(1));
return true;
}
if (cmd == YSTRING("showstatus"))
return onCmdShow(ifc,msg,YSTRING("status"));
if (cmd == YSTRING("showboardstatus"))
return onCmdShow(ifc,msg,YSTRING("boardstatus"));
if (cmd == YSTRING("showstatistics"))
return onCmdShow(ifc,msg,YSTRING("statistics"));
if (cmd == YSTRING("showtimestamps"))
return onCmdShow(ifc,msg,YSTRING("timestamps"));
if (cmd == YSTRING("showlms"))
return onCmdShow(ifc,msg,YSTRING("lms"));
if (cmd == YSTRING("show"))
return onCmdShow(ifc,msg);
if (cmd == YSTRING("freqoffs"))
return onCmdFreqOffs(ifc,msg);
if (cmd == YSTRING("freqcalstart"))
return onCmdFreqCal(ifc,msg,true);
if (cmd == YSTRING("freqcalstop")) {
ifc->device()->disableDiscipline(true);
msg.retValue() << "frequency calibration disabled";
return true;
}
bool calStop = (cmd == YSTRING("cal_stop"));
if (calStop || cmd == YSTRING("cal_abort")) {
if (!ifc->device())
return retMsgError(msg,"No device");
ifc->device()->m_calibrateStop = calStop ? 1 : -1;
return true;
}
return false;
}
bool BrfModule::onCmdStatus(String& retVal, String& line)
{
static const String s_devInfo("withdevinfo");
String ifcName;
bool devInfo = false;
if (line && getFirstStr(ifcName,line)) {
if (ifcName == s_devInfo) {
devInfo = true;
ifcName.clear();
}
else if (line) {
String tmp;
devInfo = getFirstStr(tmp,line) && (tmp == s_devInfo);
}
}
String extra;
String stats;
String info;
if (ifcName) {
stats << "interface=" << ifcName;
RefPointer<BrfInterface> ifc;
if (findIface(ifc,ifcName) && ifc->device())
ifc->device()->dumpDev(info,devInfo,true,",",true,true);
}
else {
unsigned int n = 0;
lock();
ListIterator iter(m_ifaces);
for (GenObject* gen = 0; (gen = iter.get()) != 0; ) {
RefPointer<BrfInterface> ifc = static_cast<BrfInterface*>(gen);
if (!ifc)
continue;
unlock();
n++;
BrfLibUsbDevice* dev = ifc->device();
if (dev) {
String tmp;
dev->dumpDev(tmp,devInfo,true,",",true,false);
info.append(ifc->toString(),",") << "=" << tmp;
}
lock();
}
unlock();
extra << "format=RxVGA1|RxVGA2|RxDCCorrI|RxDCCorrQ|TxVGA1|TxVGA2|"
"RxFreq|TxFreq|RxSampRate|TxSampRate|RxLpfBw|TxLpfBw|RxRF|TxRF";
if (devInfo)
extra << "|Address|Serial|Speed|Firmware|FPGA|LMS_Ver";
stats << "count=" << n;
}
retVal << "module=" << name();
retVal.append(extra,",") << ";";
if (stats)
retVal << stats << ";";
retVal << info;
retVal << "\r\n";
return true;
}
#define BRF_GET_BOOL_PARAM(bDest,param) \
const String& tmpGetBParamStr = msg[YSTRING(param)]; \
if (!tmpGetBParamStr.isBoolean()) \
return retParamError(msg,param); \
bDest = tmpGetBParamStr.toBoolean();
// control ifc_name vgagain tx=boolean mixer={1|2} [value=]
// control ifc_name {tx|rx}vga{1|2}gain [value=]
bool BrfModule::onCmdGain(BrfInterface* ifc, Message& msg, int tx, bool preMixer)
{
if (!ifc->device())
return retMsgError(msg,"No device");
const String* value = 0;
if (tx >= 0)
value = msg.getParam(YSTRING("value"));
else {
bool tmpTx = true;
BRF_GET_BOOL_PARAM(tmpTx,"tx");
const String& what = msg[YSTRING("vga")];
preMixer = (what == YSTRING("1"));
if (!preMixer && what != YSTRING("2"))
return retParamError(msg,"vga");
tx = tmpTx ? 1 : 0;
value = msg.getParam(YSTRING("gain"));
}
unsigned int code = 0;
int crt = 0;
if (!TelEngine::null(value))
code = tx ? ifc->device()->setTxVga(value->toInteger(),preMixer) :
ifc->device()->setRxVga(value->toInteger(),preMixer);
if (!code)
code = tx ? ifc->device()->getTxVga(crt,preMixer) :
ifc->device()->getRxVga(crt,preMixer);
if (code)
return retValFailure(msg,code);
msg.setParam(YSTRING("value"),String(crt));
msg.retValue() = crt;
return true;
}
// control ifc_name correction tx=boolean corr={dc-i/dc-q/fpga-gain/fpga-phase} [value=]
bool BrfModule::onCmdCorrection(BrfInterface* ifc, Message& msg, int tx, int corr)
{
if (!ifc->device())
return retMsgError(msg,"No device");
if (tx < 0) {
bool tmpTx = true;
BRF_GET_BOOL_PARAM(tmpTx,"tx");
const String& corrStr = msg[YSTRING("corr")];
if (corrStr == YSTRING("dc-i"))
corr = BrfLibUsbDevice::CorrLmsI;
else if (corrStr == YSTRING("dc-q"))
corr = BrfLibUsbDevice::CorrLmsQ;
else if (corrStr == YSTRING("fpga-phase"))
corr = BrfLibUsbDevice::CorrFpgaPhase;
else if (corrStr == YSTRING("fpga-gain"))
corr = BrfLibUsbDevice::CorrFpgaGain;
else
return retParamError(msg,"corr");
tx = tmpTx ? 1 : 0;
}
const String& value = msg[YSTRING("value")];
unsigned int code = 0;
int16_t crt = 0;
if (corr == BrfLibUsbDevice::CorrLmsI || corr == BrfLibUsbDevice::CorrLmsQ) {
bool i = (corr == BrfLibUsbDevice::CorrLmsI);
if (value)
code = ifc->device()->setDcOffset(tx,i,value.toInteger());
if (!code)
code = ifc->device()->getDcOffset(tx,i,crt);
}
else {
if (value)
code = ifc->device()->setFpgaCorr(tx,corr,value.toInteger());
if (!code)
code = ifc->device()->getFpgaCorr(tx,corr,crt);
}
if (code)
return retValFailure(msg,code);
msg.setParam(YSTRING("value"),String(crt));
msg.retValue() = crt;
return true;
}
// control ifc_name lmswrite addr= value= [resetmask=]
bool BrfModule::onCmdLmsWrite(BrfInterface* ifc, Message& msg)
{
if (!ifc->device())
return retMsgError(msg,"No device");
int addr = msg.getIntValue(YSTRING("addr"),-1);
if (addr < 0 || addr > 127)
return retParamError(msg,"addr");
int val = msg.getIntValue(YSTRING("value"),-1);
if (val < 0 || val > 255)
return retParamError(msg,"value");
const String& rstStr = msg[YSTRING("resetmask")];
unsigned int code = 0;
if (rstStr) {
uint8_t rst = (uint8_t)rstStr.toInteger();
code = ifc->device()->writeLMS(addr,val,&rst);
}
else
code = ifc->device()->writeLMS(addr,val);
if (!code)
return true;
return retValFailure(msg,code);
}
// control ifc_name bufoutput tx=boolean [count=value] [nodata=false]
bool BrfModule::onCmdBufOutput(BrfInterface* ifc, Message& msg)
{
if (!ifc->device())
return retMsgError(msg,"No device");
bool tx = true;
BRF_GET_BOOL_PARAM(tx,"tx");
ifc->device()->showBuf(tx,msg.getIntValue(YSTRING("count")),
msg.getBoolValue(YSTRING("nodata")));
return true;
}
// control ifc_name show [info=status|statistics|timestamps|boardstatus|peripheral|freqcal] [peripheral=all|list(lms,gpio,vctcxo,si5338)] [addr=] [len=]
bool BrfModule::onCmdShow(BrfInterface* ifc, Message& msg, const String& what)
{
if (!ifc->device())
return retMsgError(msg,"No device");
String info;
if (what)
info = what;
else
info = msg.getValue(YSTRING("info"),"status");
if (info == YSTRING("freqcal"))
return onCmdFreqCal(ifc,msg,false);
String str;
if (info == YSTRING("status"))
ifc->device()->dumpDev(str,true,true,"\r\n");
else if (info == YSTRING("boardstatus"))
ifc->device()->dumpBoardStatus(str,"\r\n");
else if (info == YSTRING("statistics"))
ifc->device()->dumpStats(str,"\r\n");
else if (info == YSTRING("timestamps"))
ifc->device()->dumpTimestamps(str,"\r\n");
else if (info == YSTRING("peripheral") || info == YSTRING("lms")) {
String peripheralList;
if (what)
peripheralList = what;
else {
peripheralList = msg.getValue(YSTRING("peripheral"),"all");
if (peripheralList == YSTRING("all"))
peripheralList = "lms,gpio,vctcxo,si5338";
}
uint8_t addr = (uint8_t)msg.getIntValue("addr",0,0);
uint8_t len = (uint8_t)msg.getIntValue("len",128,1);
ObjList* lst = peripheralList.split(',');
for (ObjList* o = lst->skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
s->toUpper();
for (uint8_t i = 0; i < BrfLibUsbDevice::UartDevCount; i++) {
if (*s != s_uartDev[i])
continue;
String tmp;
ifc->device()->dumpPeripheral(i,addr,len,&tmp);
if (tmp)
str.append(s_uartDev[i],"\r\n") << tmp;
break;
}
}
TelEngine::destruct(lst);
}
else
return retParamError(msg,"info");
if (str) {
char buf[50];
Debugger::formatTime(buf);
Output("Interface '%s' info=%s time=%s [%p]%s",
ifc->debugName(),info.c_str(),buf,ifc,encloseDashes(str,true));
}
return true;
}
// control ifc_name freqoffs [{value|drift}=]
bool BrfModule::onCmdFreqOffs(BrfInterface* ifc, Message& msg)
{
if (!ifc->device())
return retMsgError(msg,"No device");
const String* strRet = msg.getParam(YSTRING("value"));
if (strRet) {
float freqoffs = strRet->toDouble(-1);
if (freqoffs > 0) {
bool stop = msg.getBoolValue(YSTRING("stop"),true);
unsigned code = ifc->device()->setFreqOffset(freqoffs,0,stop);
if (code)
return retValFailure(msg,code);
}
else
return retParamError(msg,"value");
}
else if ((strRet = msg.getParam(YSTRING("drift"))) != 0) {
int drift = strRet->toInteger();
if (!drift)
return retParamError(msg,"drift");
if (!waitDisciplineFree())
return false;
ifc->device()->trimVctcxo(Time::now(),drift);
m_disciplineBusy = false;
}
msg.retValue() << "freqoffs=" << ifc->device()->freqOffset();
return true;
}
// control ifc_name freqcalstart [system_accuracy=] [count=] or control ifc_name show info=freqcal
bool BrfModule::onCmdFreqCal(BrfInterface* ifc, Message& msg, bool start)
{
if (!ifc->device())
return retMsgError(msg,"No device");
if (!waitDisciplineFree())
return false;
bool ret = ifc->device()->onCmdFreqCal(msg,start);
m_disciplineBusy = false;
return ret;
}
void BrfModule::setDebugPeripheral(const NamedList& params)
{
for (uint8_t i = 0; i < BrfLibUsbDevice::UartDevCount; i++) {
BrfPeripheral& p = s_uartDev[i];
const String& tmp = params[p.lowCase + "_debug"];
bool tx = false;
bool rx = false;
if (tmp) {
if (tmp == YSTRING("tx"))
tx = true;
else if (tmp == YSTRING("rx"))
rx = true;
else if (tmp == YSTRING("both"))
tx = rx = true;
}
p.setTrack(tx,rx,params[p.lowCase + "_trackaddr"],
params.getIntValue(p.lowCase + "_level",-1));
}
}
void BrfModule::setSampleEnergize(const String& value)
{
Lock lck(this);
int val = value.toInteger(2047);
if (val == (int)s_sampleEnergize)
return;
if (val < 1 || val > 2047) {
Debug(this,DebugConf,"Invalid sampleenergize=%s",value.c_str());
return;
}
Debug(this,DebugInfo,"sampleenergize changed %u -> %d",s_sampleEnergize,val);
s_sampleEnergize = val;
// Notify devices
ListIterator iter(m_ifaces);
for (GenObject* gen = 0; (gen = iter.get()) != 0; ) {
RefPointer<BrfInterface> ifc = static_cast<BrfInterface*>(gen);
if (!ifc)
continue;
lck.drop();
// Just set flags used by the device to update data using sample energize value
if (ifc->device()) {
ifc->device()->m_txPowerBalanceChanged = true;
ifc->device()->m_txPatternChanged = true;
}
ifc = 0;
lck.acquire(this);
}
}
//
// BrfThread
//
// Device thread
BrfThread::BrfThread(BrfLibUsbDevice* dev, int type, const NamedList& params,
const char* name, Thread::Priority prio)
: Thread(name,prio),
m_type(type),
m_params(params),
m_device(dev),
m_paused(false),
m_pauseToggle(false),
m_priority(Thread::priority(prio))
{
m_params.assign(name);
}
// Start this thread. Set destination pointer on success. Delete object on failure
BrfThread* BrfThread::start()
{
if (startup())
return this;
Debug(ifc(),DebugNote,"Failed to start worker '%s' [%p]",name(),ifc());
delete this;
return 0;
}
// Pause / resume a thread
unsigned int BrfThread::pauseToggle(BrfThread*& th, Mutex* mtx, bool on, String* error)
{
Lock lck(mtx);
if (!th)
return BrfLibUsbDevice::setErrorFail(error,"Worker abnormally terminated");
if (th->m_paused == on)
return 0;
th->m_pauseToggle = true;
lck.drop();
for (unsigned int n = threadIdleIntervals(200); n; n--) {
Thread::idle();
Lock lck(mtx);
if (!th)
return BrfLibUsbDevice::setErrorFail(error,"Worker abnormally terminated");
if (!th->m_pauseToggle)
return 0;
if (th->m_device) {
unsigned int status = th->m_device->cancelled(error);
if (status)
return status;
}
else if (Thread::check(false))
return BrfLibUsbDevice::setError(RadioInterface::Cancelled,error,"Cancelled");
}
return BrfLibUsbDevice::setErrorTimeout(error,"Worker pause toggle timeout");
}
void BrfThread::cancelThread(BrfThread*& th, Mutex* mtx, unsigned int waitMs,
DebugEnabler* dbg, void* ptr)
{
if (!th)
return;
Lock lck(mtx);
if (!th)
return;
//Debugger d(DebugAll,"BrfThread::cancelThread()"," [%p]",ptr);
th->cancel();
lck.drop();
if (!waitMs)
return;
unsigned int intervals = threadIdleIntervals(waitMs);
bool cancelled = Thread::check(false);
while (th && intervals-- && (cancelled || !Thread::check(false)))
Thread::idle();
if (!th)
return;
lck.acquire(mtx);
if (!th)
return;
Debug(dbg,DebugWarn,"Hard cancelling (%p) '%s' worker [%p]",th,th->name(),ptr);
th->cancel(true);
th = 0;
}
void BrfThread::run()
{
if (!m_device)
return;
Debug(ifc(),DebugAll,"Worker (%p) '%s' started prio=%s [%p]",
this,name(),m_priority,ifc());
switch (m_type) {
case DevCalibrate:
m_device->calibrate(true,m_params,0,true);
break;
case DevSend:
m_device->runSend(this);
break;
case DevRecv:
m_device->runRecv(this);
break;
default:
;
}
notify();
}
void BrfThread::notify()
{
BrfLibUsbDevice* dev = m_device;
m_device = 0;
if (!dev)
return;
bool ok = ((String)m_params == Thread::currentName());
Debug(dev->owner(),ok ? DebugAll : DebugWarn,"Worker (%p) '%s' terminated [%p]",
this,name(),dev->owner());
Lock lck(dev->m_threadMutex);
if (dev->m_calThread == this)
dev->m_calThread = 0;
else if (dev->m_sendThread == this)
dev->m_sendThread = 0;
else if (dev->m_recvThread == this)
dev->m_recvThread = 0;
}
// amplifier linearization functions
// mean complex gain between two vectors
static Complex meanComplexGain(const Complex* rx, const Complex* tx, unsigned length)
{
// no data? unity gain
if (!length)
return Complex(1,0);
Complex sum = 0;
unsigned count = 0;
for (unsigned i=0; i<length; i++) {
if (i<8)
Debug(DebugAll,"meanComplexGain rx[%u]=%f%+f tx[%u]=%f%+f",
i,rx[i].re(),rx[i].im(),i,tx[i].re(),tx[i].im());
Complex gain = rx[i] / tx[i];
sum += gain;
count++;
}
return sum / length;
}
static unsigned findBreakAndSlope(const FloatVector& v, float startdB, float stepdB, float *bp, float *slope)
{
if (v.length()==0) {
Debug(DebugWarn,"findBreakAndSlope zero length vector");
return -1;
}
unsigned imax = v.length()-1;
// get the last two power values
float lastdB = startdB + stepdB*imax;
float pmax = pow(10,lastdB*0.1);
float pmax_1 = pow(10,(lastdB-stepdB)*0.1);
// slope at high end of the scale
// defines a line through the two two samples
*slope = (v[imax] - v[imax-1]) / (pmax - pmax_1);
// breakpoint is the intersection of the two lines
*bp = pmax - (v[imax] - v[0]) / (*slope);
return 0;
}
unsigned BrfLibUsbDevice::findGainExpParams(const ComplexVector& sweep, float startdB, float stepdB)
{
FloatVector gain(sweep.length());
for (unsigned i=0; i<gain.length(); i++)
gain[i] = sweep[i].norm2();
if (findBreakAndSlope(gain, startdB, stepdB, &m_gainExpBreak, &m_gainExpSlope) < 0)
return -1;
Debug(m_owner,DebugInfo,"amp gain expansion: bp = %f linear slope = %f linear [%p]",
m_gainExpBreak,m_gainExpSlope,this);
return 0;
}
unsigned BrfLibUsbDevice::findPhaseExpParams(const ComplexVector& sweep, float startdB, float stepdB)
{
FloatVector phase(sweep.length());
for (unsigned i=0; i<phase.length(); i++)
phase[i] = sweep[i].arg();
if (findBreakAndSlope(phase, startdB, stepdB, &m_phaseExpBreak, &m_phaseExpSlope) < 0)
return -1;
Debug(m_owner,DebugInfo,"amp phase expansion: bp = %f linear slope = %f deg/lin [%p]",
m_phaseExpBreak,180*m_phaseExpSlope/M_PI,this);
return 0;
}
// sweep function
// sweeps power over a range and records gain and phase of the loopback signal
ComplexVector BrfLibUsbDevice::sweepPower(float startdB, float stopdB, float stepdB)
{
Debug(m_owner,DebugInfo,"sweepPower start=%4.2f stop=%4.2f step=%4.2f",
startdB, stopdB, stepdB);
unsigned steps = 1 + (unsigned)((stopdB-startdB)/stepdB);
ComplexVector sweep(steps);
Complex rxBuf[2004];
unsigned int status = 0;
String e;
for (unsigned step=0; step<steps; step++) {
// set up the reference signal
float dB = startdB + stepdB*step;
float gain = pow(10,dB/10);
setTxPattern("circle",gain);
// receive the amp output
Thread::msleep(10);
BRF_FUNC_CALL_BREAK(setStateSyncTx(0,&e));
uint64_t ts = m_syncTxState.m_tx.m_timestamp + m_radioCaps.rxLatency;
BRF_FUNC_CALL_BREAK(capture(false,(float*)rxBuf,2004,ts,&e));
// calculate and save the gain
unsigned base = (4 - (ts % 4)) % 4;
Complex sGain = meanComplexGain(rxBuf+2*base, m_txPatternBuffer.data(), 2000);
Debug(m_owner,DebugAll,"sweepPower[%u] result=(%g,%g) when=" FMT64U " base=%u"
" power=%4.2f (%4.2f linear) gain=%4.2f dB @ %4.2f deg",
step,sGain.re(),sGain.im(),ts,base,dB,gain,
10*log10(sGain.norm2()),sGain.arg()*180/M_PI);
sweep[step] = sGain;
}
if (status) {
Debug(m_owner,DebugWarn,"sweep: %u %s",status,e.c_str());
sweep.resetStorage(0);
}
return sweep;
}
// generic expansion function
// returns the expansion factor for this power level x
// x and breakpoint in linear power units
// slope in units of <whatever> / <linear power>
// expansion factor in units of <whatever>, determined by the units of the slope
inline float expansion(float x, float breakpoint, float slope)
{
const float delta = x - breakpoint;
if (delta<0) return 0;
return delta*slope;
}
// recaluclate the amplifier linearization table
// should be called any time an amplifier linearization parameter changes
unsigned BrfLibUsbDevice::calculateAmpTable()
{
float maxGain = 1 + expansion(2, m_gainExpBreak, m_gainExpSlope);
float maxPhase = expansion(2, m_phaseExpBreak, m_phaseExpSlope);
float midGain = 1 + expansion(1, m_gainExpBreak, m_gainExpSlope);
float midPhase = expansion(1, m_phaseExpBreak, m_phaseExpSlope);
Debug(m_owner,DebugInfo,
"calculateAmpTable gBp=%4.2f gS=%4.2f g0=%4.2f gMax=%4.2f "
"pBp=%4.2f pS=%+4.2f p0=%+4.2f deg pMax=%+4.2f deg",
m_gainExpBreak, m_gainExpSlope, midGain, maxGain, m_phaseExpBreak,
m_phaseExpSlope*180/M_PI, midPhase*180/M_PI, maxPhase*180/M_PI);
for (unsigned i=0; i<2*2048; i++) {
// normalized power level (0..2)
float p = ((float)i) / 2048.0F;
// base gain is 1 - this is where we compensate the 1 we subtracted from the max
float gainExp = 1 + expansion(p, m_gainExpBreak, m_gainExpSlope);
float phaseExp = expansion(p, m_phaseExpBreak, m_phaseExpSlope);
Complex c(0,phaseExp);
float adjGain = gainExp / maxGain;
Complex adjust = c.exp() * adjGain;
m_ampTable[2*i] = 2048*adjust.re();
m_ampTable[2*i+1] = 2048*adjust.im();
}
m_ampTableUse = true;
return 0;
}
}; // anonymous namespace
/* vi: set ts=8 sw=4 sts=4 noet enc=utf-8: */