/** * 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 Null Team * Copyright (C) 2015 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 #include #include #include #include #include //#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 0xffffffffu // Frequency bounds #define BRF_FREQUENCY_MIN 232500000u #define BRF_FREQUENCY_MAX 3800000000u // Frequency offset interval #define BRF_FREQ_OFFS_DEF 128 #define BRF_FREQ_OFFS_MIN 64 #define BRF_FREQ_OFFS_MAX 192 #define BRF_RXVGA1_GAIN_MIN 5 #define BRF_RXVGA1_GAIN_MAX 30 #define BRF_RXVGA2_GAIN_MIN 0 #define BRF_RXVGA2_GAIN_MAX 30 #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 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,0,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_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* 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 String& appendComplex(String& s, float* f, unsigned int n) { char c[320]; n /= 2; if (!(f && n)) return s; String tmp; unsigned int a = n / 4; while (a--) { ::sprintf(c,"(%.3f,%.3f) (%.3f,%.3f) (%.3f,%.3f) (%.3f,%.3f)",f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7]); f += 8; tmp.append(c," "); } a = n % 4; while (a--) { ::sprintf(c,"(%.3f,%.3f)",f[0],f[1]); f += 2; tmp.append(c," "); } return s.append(tmp); } static inline String& appendComplex(String& s, DataBlock& d) { return appendComplex(s,(float*)d.data(0),d.length() / sizeof(float)); } 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)); } 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_MIN 1500000u #define BRF_FILTER_BW_MAX 20000000u static uint32_t s_bandSet[] = { BRF_FILTER_BW_MIN, 1750000u, 2500000u, 2750000u, 3000000u, 3840000u, 5000000u, 5500000u, 6000000u, 7000000u, 8750000u, 10000000u,12000000u,14000000u,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) // Read samples from string static bool readSamples(DataBlock& buf, const String& list) { ObjList* l = list.split(','); unsigned int n = l->count(); if (n < 2 || (n % 2) != 0) { TelEngine::destruct(l); return false; } buf.resize(n * sizeof(float)); float* f = (float*)buf.data(0); for (ObjList* o = l->skipNull(); f && o; o = o->skipNext()) { *f = static_cast(o->get())->toDouble(); if (*f >= -1 && *f <= 1) f++; else f = 0; } TelEngine::destruct(l); if (!f) buf.clear(); return f != 0; } 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)); } static inline int16_t energize(float value, float scale, int16_t refVal, unsigned int& clamp) { int16_t v = sampleScale(value,scale); if (v > refVal) { clamp++; return refVal; } if (v < -refVal) { clamp++; return -refVal; } return v; } static inline void brfCopyTxData(int16_t* dest, float* src, unsigned int samples, float scaleI, int16_t maxI, float scaleQ, int16_t maxQ, unsigned int& clamped) { for (; samples; samples--) { *dest++ = htole16(energize(*src++,scaleI,maxI,clamped)); *dest++ = htole16(energize(*src++,scaleQ,maxQ,clamped)); } } 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 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) { lowCase = name; lowCase.toLower(); set(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; } void set(bool tx, bool rx, const String& addr = String::empty()); // 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]; }; // 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; }; // 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 BrfDevStatus { public: inline BrfDevStatus(bool tx) : 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_tx(tx) {} inline BrfDevStatus(const BrfDevStatus& src) { *this = src; } inline BrfDevStatus& operator=(const BrfDevStatus& src) { ::memcpy(this,&src,sizeof(src)); return *this; } inline bool tx() const { return m_tx; } bool rfEnabled; // RF enabled flag unsigned int frequency; // Used frequency unsigned int freqOffset; // Used frequency offset 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 protected: bool m_tx; // Direction }; // Holds RX/TX related data // Hold samples read/write related data class BrfDevIO : public BrfDevStatus { public: inline BrfDevIO(bool tx) : BrfDevStatus(tx), showBuf(0), showBufData(true), checkTs(0), mutex(false,tx ? "BrfDevIoTx" : "BrfDevIoRx"), showDcOffsChange(0), showFpgaPhaseChange(0), showPowerBalanceChange(0), startTime(0), transferred(0), timestamp(0), lastTs(0), buffers(0), hdrLen(0), bufSamples(0), bufSamplesLen(0), crtBuf(0), crtBufSampOffs(0), newBuffer(true), syncFlags(0), syncTs(0), syncStatus(tx), dataDumpParams(""), dataDump(0), dataDumpFile(brfDir(tx)), upDumpParams(""), upDump(0), upDumpFile(String(brfDir(tx)) + "-APP"), #ifdef LITTLE_ENDIAN m_bufEndianOk(true) #else m_bufEndianOk(false) #endif {} 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 setRf(bool on) { rfEnabled = on; syncFlags = 0; 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; } #ifdef LITTLE_ENDIAN inline void fixEndian() {} #else inline void fixEndian() { 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 Mutex mutex; // Protect data changes when needed unsigned int showDcOffsChange; // Show DC offset changed debug message unsigned int showFpgaPhaseChange; // Show FPGA PHASE changed debug message unsigned int showPowerBalanceChange; // Show power balance changed debug message 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 // Sync timestamp unsigned int syncFlags; uint64_t syncTs; BrfDevStatus syncStatus; // File dump NamedList dataDumpParams; int dataDump; RadioDataFile dataDumpFile; NamedList upDumpParams; int upDump; RadioDataFile upDumpFile; protected: // Reset current buffer to start inline void setCrtBuf(unsigned int index = 0) { crtBuf = index; crtBufSampOffs = 0; } 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; }; class BrfThread; class BrfSerialize; class BrfLibUsbDevice : public GenObject { friend class BrfDevTmpAltSet; friend class BrfThread; friend class BrfModule; friend class BrfSerialize; 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) }; // 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, }; enum CorrectionType { CorrLmsI, CorrLmsQ, CorrFpgaPhase, CorrFpgaGain, }; // 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 = 0x0001, DevStatVga1 = 0x0002, DevStatVga2 = 0x0004, DevStatLpf = 0x0008, DevStatDcI = 0x0010, DevStatDcQ = 0x0020, DevStatLpfBw = 0x0040, DevStatSampleRate = 0x0080, DevStatFpgaPhase = 0x0100, DevStatFpgaGain = 0x0200, DevStatPowerBalance = 0x0400, DevStatTs = 0x1000, DevStatVga = DevStatVga1 | DevStatVga2, DevStatDc = DevStatDcI | DevStatDcQ, }; ~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 int bus() const { return m_devBus; } inline int addr() const { return m_devAddr; } 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 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 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; } unsigned int setTxPattern(const String& pattern); 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); // Initialize the device. // Call the reset method in order to set the device to a known state bool open(const NamedList& params); // 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); // 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); // Set the frequency on the Tx or Rx side unsigned int setFrequency(uint32_t hz, bool tx); // Retrieve frequency unsigned int getFrequency(uint32_t& hz, bool tx); // Set frequency offset unsigned int setFreqOffset(int offs, int* newVal = 0); // Get frequency offset unsigned int getFreqOffset(int& 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); // Auto calibrate unsigned int calibrate(); // 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); // Write LMS register unsigned int writeLMS(uint8_t addr, uint8_t value, uint8_t* rst = 0); // Enable or disable loopback unsigned int setLoopback(const char* name = 0); unsigned int setLoopback(int mode = 0); // Release data virtual void destruct(); // Create an interface static BrfLibUsbDevice* create(BrfInterface* owner); // 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 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 int fpgaCorrAddr(bool tx, int corr, String& what) { if (corr == CorrFpgaPhase) { what = "PHASE"; return tx ? 10 : 6; } if (corr == CorrFpgaGain) { what = "GAIN"; return tx ? 8 : 4; } what = corr; return -1; } // 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; } private: BrfLibUsbDevice(BrfInterface* owner); inline void doClose() { m_closing = true; closeDevice(); clearDeviceList(); m_closing = false; } unsigned int setStatus(const BrfDevStatus& stat, unsigned int flags, String* error = 0); unsigned int setStatus(const BrfDevStatus& statTx, unsigned int flagsTx, const BrfDevStatus& statRx, unsigned int flagsRx, String* error = 0) { unsigned int status = setStatus(statRx,flagsRx,error); return status == 0 ? setStatus(statTx,flagsRx,error) : status; } 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); unsigned int recv(uint64_t& ts, float* data, unsigned int& samples); unsigned int internalSetSampleRate(bool tx, uint32_t value, String* error = 0); // 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); 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(int val, int* newVal, String* error = 0); unsigned int internalSetFrequency(bool tx, uint32_t hz, 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 ? LmsPa1 : LmsPa2,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, 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); 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; } unsigned int enableRf(bool tx, bool on, bool frontEndOnly = false, String* error = 0); // Read the FPGA version unsigned int getFpgaVersion(uint32_t& version); // 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); // 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); unsigned int paSelect(int pa, String* error = 0); void clampFrequency(uint32_t& val, bool tx, const char* loc); int clampInt(int val, int minVal, int maxVal, const char* what = 0, int level = DebugNote); inline int clampIntParam(const NamedList& params, const String& param, int defVal, int minVal, int maxVal, int level = DebugConf) { return clampInt(params.getIntValue(param,defVal),minVal,maxVal,param,level); } unsigned int openDevice(bool claim, String* error = 0); void closeDevice(); 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); // Compute Rx avg values, autocorrect offsets if configured void computeRx(uint64_t ts); // Check io timestamps void ioBufCheckTs(bool tx, unsigned int nBufs = 0); // Alter data void updateAlterData(const NamedList& params); void rxAlterData(bool first); // Calibration utilities 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); // Read data, ignore any buffer related errors unsigned int dummyRead(float* buf, unsigned int samples, String* error); unsigned int readBuffer(uint64_t ts, float* buf, unsigned int samples, String* error); unsigned int readComputeDcOffsets(uint8_t dcI, uint8_t dcQ, float* buf, unsigned int samples, String* error, float& totalPower, float& rxDcOffset, float& txDcOffset); unsigned int readComputeDcOffsetsCorr(int* corr, float* powerBalance, float* buf, unsigned int samples, String* error, float& totalPower, float& rxDcOffset); void calibrateBBStarting(const char* what); unsigned int calibrateBBTxDc(int& dcI, int& dcQ, float* buf, unsigned int samples, String* error); unsigned int calibrateBBTxPhase(bool bruteForce, int& corr, float* buf, unsigned int samples, String* error); unsigned int calibrateBBTxGain(bool bruteForce, float& corr, float* buf, unsigned int samples, String* error); unsigned int calibrateBB(String* error); // 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 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, String* error = 0); unsigned int setLoopbackPath(int mode, String& error); unsigned int enableRfLoopback(bool on, String& error); void dumpLoopbackStatus(); void dumpLmsModulesStatus(); 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); } // Request changes (synchronous TX). Wait for change inline unsigned int syncTxStatus(unsigned int flagsTx, unsigned int flagsRx, String* error = 0) { m_txIO.syncFlags = flagsTx; m_rxIO.syncFlags = flagsRx; unsigned int intervals = (m_bulkTout / Thread::idleMsec()) + 1; unsigned int status = 0; while (m_txIO.syncFlags || m_rxIO.syncFlags) { Thread::idle(); BRF_FUNC_CALL_RET(cancelled(error)); if ((intervals--) == 0) { m_txIO.syncFlags = 0; m_rxIO.syncFlags = 0; return setError(RadioInterface::Failure,error,"Sync TS timeout"); } } return 0; } inline bool setRxDcAuto(bool value) { if (m_rxDcAuto != value) return !(m_rxDcAuto = value); return m_rxDcAuto; } BrfInterface* m_owner; // The interface owning the device bool m_exiting; // Exiting flag bool m_closing; // Closing flag bool m_closingDevice; // Closing device flag 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_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 uint16_t m_ctrlTransferPage; // Control transfer page size DataBlock m_calCache; // NamedList m_calibration; // Calibration parameters 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 bool m_rxDcAuto; // Automatically adjust Rx DC offset 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 int m_freqOffset; // Master clock frequency adjustment BrfDevIO m_txIO; BrfDevIO m_rxIO; LusbTransfer m_usbTransfer[EpCount]; // List of USB transfers int m_loopback; // Current loopback mode 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 bool m_calibrated; 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; // 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; String m_txPatternStr; DataBlock m_txPattern; DataBlock m_txPatternBuffer; unsigned int m_txPatternBufPos; unsigned int m_txPatternBufSamples; // Calibration BrfThread* m_sendThread; Mutex m_sendThreadMutex; }; // 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 void wait() { if (m_lock.acquire(m_io.mutex)) { if ((status = m_device->cancelled()) != 0) drop(); } else status = m_device->showError(RadioInterface::Failure, "Failed to serialize",brfDir(m_io.tx()),0,DebugWarn); } 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(); } 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(int offs, int* 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(); } protected: BrfInterface(const char* name); // Method to call after creation to init the interface virtual BrfLibUsbDevice* init(); virtual void destroyed(); private: BrfLibUsbDevice* m_dev; // Used device int m_txFreqCorr; }; class BrfThread : public Thread { public: inline BrfThread(BrfInterface* ifc, const char* name) : Thread(name), m_name(name), m_iface(ifc) {} // Device send thread inline BrfThread(BrfLibUsbDevice* dev, const char* name = "BrfDevSend") : Thread(name), m_name(name), m_iface(0), m_device(dev) {} ~BrfThread() { notify(); } inline const char* name() const { return m_name; } virtual void cleanup() { notify(); } // Start this thread. Set destination pointer on success. Delete object on failure BrfThread* start(); // Stop thread static void cancelThread(BrfThread*& th, Mutex* mtx, unsigned int waitMs, DebugEnabler* dbg, void* ptr); protected: virtual void run(); void notify(); String m_name; RefPointer m_iface; BrfLibUsbDevice* m_device; }; class BrfTest : public BrfInterface, public Mutex { YCLASS(BrfTest,BrfInterface) friend class BrfModule; friend class BrfThread; public: enum State { Idle = 0, Running, Stopping }; BrfTest(const char* name, const NamedList& params, const NamedList& devOpen, const NamedList& cmds); inline int state() const { return m_state; } inline void pause() { m_pause = true; Thread::msleep(100); } inline void resume() { if (!m_pause) return; Thread::msleep(100); updateTs(true); updateTs(false); m_pause = false; } bool start(); void stop(); bool execute(const String& cmd, const String& param, String& error, bool fatal, const NamedList* params = 0); protected: bool execute(const NamedList& cmds, const char* prefix, String& error); bool runInit(); void run(); void runSendOnly(); void runSendRecv(); void dumpStats(); bool workerTerminated(BrfThread* th); bool checkPause(bool tx); bool write(); bool read(); inline void updateTs(bool tx) { uint64_t ts = 0; if ((tx ? getTxTime(ts) : getRxTime(ts)) == 0) (tx ? m_sendTs : m_readTs) = ts; } inline void resetBufs(unsigned int samples) { m_bufs.reset(samples,0); m_crt.assign(0,samplesf2bytes(m_bufs.bufSamples())); m_aux = m_crt; m_extra = m_crt; m_bufs.crt.samples = (float*)m_crt.data(0); m_bufs.aux.samples = (float*)m_aux.data(0); m_bufs.extra.samples = (float*)m_extra.data(0); } int m_state; bool m_pause; bool m_pauseSend; bool m_pauseRead; unsigned int m_sendBufCount; bool m_sendOnly; BrfThread* m_worker; // Send data uint64_t m_sentSamples; uint64_t m_sendTs; DataBlock m_sendBufData; float* m_sendBuf; unsigned int m_sendBufSamples; // Read data RadioReadBufs m_bufs; uint64_t m_readTs; unsigned int m_skippedBuffs; DataBlock m_crt; DataBlock m_aux; DataBlock m_extra; // Params NamedList m_params; NamedList m_devOpen; NamedList m_cmds; }; class BrfModule : public Module { friend class BrfInterface; public: enum Relay { RadioCreate = Private, }; BrfModule(); ~BrfModule(); bool findIfaceByDevice(RefPointer& iface, void* dev); inline bool findIface(RefPointer& iface, const String& n) { Lock lck(this); ObjList* o = m_ifaces.find(n); if (o) iface = static_cast(o->get()); return iface != 0; } inline void setTest(bool on, BrfTest* ptr = 0) { Lock lck(this); if (on) { m_test = 0; Lock lck(ptr); if (ptr->state() == BrfTest::Running) m_test = ptr; } else if (ptr && m_test == ptr) m_test = 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); BrfInterface* createIface(const 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 test(const String& cmd, NamedList& list); void setDebugPeripheral(const NamedList& list); void setSampleEnergize(const String& value); unsigned int m_ifaceId; ObjList m_ifaces; RefPointer m_test; }; static bool s_usbContextInit = false; // USB library init flag INIT_PLUGIN(BrfModule); static Configuration s_cfg; // Configuration file (protected by plugin mutex) static const String s_modCmds[] = {"test","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",""}; // libusb 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 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(bool safe = true, NamedList* s1 = 0) { Lock lck(safe ? 0 : &__plugin); s_cfg = Engine::configFile("ybladerf"); s_cfg.load(); if (!TelEngine::null(s1)) { NamedList* tmp = s_cfg.getSection(*s1); if (tmp) *s1 = *tmp; else s1->assign(""); } } 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 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); t->status = BrfLibUsbDevice::lusbCheckSuccess(transfer->status,&t->error); t->running(false); } // // BrfPeripheral // void BrfPeripheral::set(bool tx, bool rx, const String& addr) { 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_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", c_str(),String::boolText(m_tx),String::boolText(m_rx),ta.safe()); } else Debug(&__plugin,DebugAll,"%s peripheral debug is disabled",c_str()); } // // 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 "); } // // 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(),DebugGoOn, "Failed to restore alt interface after '%s': %s [%p]", m_oper,e.c_str(),m_device->owner()); m_tmpAltSet = BRF_ALTSET_INVALID; return status; } // // BrfLibUsbDevice // #define BRF_CHECK_DEV(text) { \ if (!m_devHandle) { \ Debug(m_owner,DebugGoOn,"%s: not open [%p]",text,m_owner); \ return RadioInterface::NotInitialized; \ } \ } #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_NONE BRF_TX_SERIALIZE_(true,return) #define BRF_TX_SERIALIZE_BOOL BRF_TX_SERIALIZE_(true,return false) #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_NONE BRF_RX_SERIALIZE_(true,return) #define BRF_RX_SERIALIZE_BOOL BRF_RX_SERIALIZE_(true,return false) BrfLibUsbDevice::BrfLibUsbDevice(BrfInterface* owner) : m_owner(owner), m_exiting(false), m_closing(false), m_closingDevice(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_calibration(""), m_ctrlTout(s_lusbCtrlTransferTout), m_bulkTout(s_lusbBulkTransferTout), m_altSetting(BRF_ALTSET_INVALID), m_rxShowDcInfo(0), m_rxDcAuto(true), m_rxDcOffsetMax(BRF_RX_DC_OFFSET_DEF), m_rxDcAvgI(0), m_rxDcAvgQ(0), m_freqOffset(BRF_FREQ_OFFS_DEF), m_txIO(true), m_rxIO(false), m_loopback(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_calibrated(false), 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_rxAlterDataParams(""), m_rxAlterData(false), m_rxAlterIncrement(0), m_rxAlterTsJumpSingle(true), m_rxAlterTsJumpPos(0), m_txPatternChanged(false), m_txPatternBufPos(0), m_txPatternBufSamples(0), m_sendThread(0), m_sendThreadMutex("BrfDevSendThread") { 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_rxIO.vga1 = BRF_RXVGA1_GAIN_MAX + 1; m_rxIO.dcOffsetI = BRF_RX_DC_OFFSET_MAX + 1; m_rxIO.dcOffsetQ = BRF_RX_DC_OFFSET_MAX + 1; m_txIO.vga1 = BRF_TXVGA1_GAIN_MIN - 1; m_txIO.vga2 = BRF_TXVGA2_GAIN_MIN - 1; m_txIO.dcOffsetI = BRF_RX_DC_OFFSET_MAX + 1; m_txIO.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) { Lock lck(m_dbgMutex); if (m_txPatternStr == pattern) return 0; bool hadData = (m_txPattern.length() != 0); m_txPattern.clear(); m_txPatternStr.clear(); m_txPatternChanged = true; String tmp; bool readArray = true; bool dumpArray = false; if (pattern == "circle") tmp = "1,0,0,1,-1,0,0,-1"; else if (pattern == "zero") tmp = "0,0"; else if (pattern == "random") { readArray = false; unsigned int samples = totalSamples(true); m_txPattern.resize(samplesf2bytes(samples)); float* f = (float*)m_txPattern.data(0); for (float* last = f + m_txPattern.length() / sizeof(float); f != last; f++) { long int r = Random::random(); if (!r) continue; uint64_t v = (r >= 0) ? r : -r; *f = (float)(((v % 2) == 0) ? 1 : -1) * (v % 2047) / 2047; } //dumpArray = true; } else if (pattern == "increment") { readArray = false; unsigned int samples = 557; m_txPattern.resize(samplesf2bytes(samples)); float* f = (float*)m_txPattern.data(0); for (unsigned int i = 1; i <= samples; i++, f += 2) f[0] = f[1] = (float)i / 2047; //dumpArray = true; } const String* p = tmp ? (const String*)&tmp : &pattern; if (readArray && *p && !readSamples(m_txPattern,*p)) { Debug(m_owner,DebugNote,"Invalid tx pattern '%s' [%p]",p->c_str(),m_owner); return RadioInterface::Failure; } if (m_txPattern.length()) { m_txPatternStr = pattern; String s; if (dumpArray) { appendComplex(s,m_txPattern); encloseDashes(s,true); } Debug(m_owner,DebugInfo,"TX pattern set to '%s' [%p]%s", m_txPatternStr.c_str(),m_owner,s.safe()); } else if (hadData) Debug(m_owner,DebugInfo,"TX pattern cleared [%p]",m_owner); return 0; } 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; BRF_CHECK_DEV("dumpPeripheral()"); addr = clampInt(addr,0,0x7f); len = clampInt(len,1,128 - 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)); updateAlterData(*params); } // 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 bool BrfLibUsbDevice::open(const NamedList& params) { BRF_RX_SERIALIZE_BOOL; BRF_TX_SERIALIZE_BOOL; doClose(); String e; unsigned int status = 0; while (true) { 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; } status = lusbSetAltInterface(BRF_ALTSET_IDLE,&e); if (status) break; m_freqOffset = clampIntParam(params,"RadioFrequencyOffset", BRF_FREQ_OFFS_DEF,BRF_FREQ_OFFS_MIN,BRF_FREQ_OFFS_MAX); // Init TX/RX buffers m_rxDcAuto = params.getBoolValue("rx_dc_autocorrect",true); m_rxShowDcInfo = params.getIntValue("rx_dc_showinfo"); m_rxDcOffsetMax = BRF_RX_DC_OFFSET_DEF; m_rxIO.dcOffsetI = BRF_RX_DC_OFFSET_MAX + 1; m_rxIO.dcOffsetQ = BRF_RX_DC_OFFSET_MAX + 1; int tmpInt = 0; int i = 0; int q = 0; m_rxResyncCandidate = 0; m_rxTsPastIntervalMs = clampIntParam(params,"rx_ts_past_error_interval",200,50,10000); #if 1 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)); #endif 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_rxIO.vga1 = BRF_RXVGA1_GAIN_MAX + 1; BRF_FUNC_CALL_BREAK(internalSetGain(false,BRF_RXVGA2_GAIN_MIN)); // Pre/post mixer TX VGA m_txIO.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 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); break; } if (status) { Debug(m_owner,DebugWarn,"Failed to open USB device: %s [%p]", e.safe("Unknown error"),m_owner); doClose(); return false; } 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(¶ms); return true; } // 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_BOOL; BRF_TX_SERIALIZE_BOOL; BRF_CHECK_DEV("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) { BRF_TX_SERIALIZE; BRF_CHECK_DEV("syncTx()"); unsigned int code = send(ts,data,samples,powerScale); if (code == RadioInterface::HardwareIOError) { txSerialize.drop(); Thread::yield(); } return code; } // Receive data from the Rx interface of the bladeRF device unsigned int BrfLibUsbDevice::syncRx(uint64_t& ts, float* data, unsigned int& samples) { BRF_RX_SERIALIZE; BRF_CHECK_DEV("syncRx()"); unsigned int code = recv(ts,data,samples); if (code == RadioInterface::HardwareIOError) { rxSerialize.drop(); Thread::yield(); } return code; } unsigned int BrfLibUsbDevice::setFrequency(uint32_t hz, bool tx) { BRF_TX_SERIALIZE; BRF_CHECK_DEV("setFrequency()"); return internalSetFrequency(tx,hz); } unsigned int BrfLibUsbDevice::getFrequency(uint32_t& hz, bool tx) { BRF_TX_SERIALIZE; BRF_CHECK_DEV("getFrequency()"); return internalGetFrequency(tx,&hz); } // Set frequency offset unsigned int BrfLibUsbDevice::setFreqOffset(int offs, int* newVal) { BRF_TX_SERIALIZE; BRF_CHECK_DEV("setFreqOffset()"); return internalSetFreqOffs(offs,newVal); } // Get frequency offset unsigned int BrfLibUsbDevice::getFreqOffset(int& offs) { BRF_TX_SERIALIZE; BRF_CHECK_DEV("getFreqOffset()"); String val; unsigned int status = getCalField(val,"DAC","DAC_TRIM"); if (status == 0) offs = val.toInteger(); return status; } // Set the bandwidth for a specific module unsigned int BrfLibUsbDevice::setLpfBandwidth(uint32_t band, bool tx) { BRF_TX_SERIALIZE; BRF_CHECK_DEV("setLpfBandwidth()"); return internalSetLpfBandwidth(tx,band); } // Get the bandwidth for a specific module unsigned int BrfLibUsbDevice::getLpfBandwidth(uint32_t& band, bool tx) { BRF_TX_SERIALIZE; BRF_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 = s_bandSet[15 - data]; getIO(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; BRF_CHECK_DEV("setLpf()"); return internalSetLpf(tx,lpf); } unsigned int BrfLibUsbDevice::getLpf(int& lpf, bool tx) { BRF_TX_SERIALIZE; BRF_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; BRF_CHECK_DEV("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; BRF_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< 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; getIO(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; BRF_CHECK_DEV("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; BRF_CHECK_DEV("getTxVga()"); return internalGetTxVga(&vga,preMixer); } // Set TX power balance unsigned int BrfLibUsbDevice::setTxIQBalance(float value) { BRF_TX_SERIALIZE; BRF_CHECK_DEV("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; BRF_CHECK_DEV("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; BRF_CHECK_DEV("setRxVga()"); return internalSetRxVga(vga,preMixer); } // Retrieve the pre/post mixer rx gain setting unsigned int BrfLibUsbDevice::getRxVga(int& vga, bool preMixer) { BRF_TX_SERIALIZE; BRF_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; BRF_CHECK_DEV("setGain()"); return internalSetGain(tx,val,newVal); } // Auto calibrate DC offsets unsigned int BrfLibUsbDevice::calibrate() { BRF_RX_SERIALIZE; BRF_TX_SERIALIZE; BRF_CHECK_DEV("calibrate()"); #ifdef DEBUG Debugger d(DebugAll,"CALIBRATE"," %s [%p]",m_owner->debugName(),m_owner); #endif Debug(m_owner,DebugInfo,"Calibrating ... [%p]",m_owner); String e; unsigned int status = calibrateAuto(&e); if (!status) status = calibrateBB(&e); m_calibrated = (status == 0); updateStatus(); if (status) { if (status == RadioInterface::Cancelled) Debug(m_owner,DebugInfo,"Calibration cancelled [%p]",m_owner); else Debug(m_owner,DebugWarn,"Calibration failed: %s [%p]",e.c_str(),m_owner); return status; } Debug(m_owner,DebugInfo,"Calibration finished [%p]",m_owner); return 0; } // Set Tx/Rx DC I/Q offset correction unsigned int BrfLibUsbDevice::setDcOffset(bool tx, bool i, int16_t value) { int rxDcAutoRestore = -1; // Temporary disable RX auto correct if (!tx) { BRF_RX_SERIALIZE; BRF_CHECK_DEV("setDcOffset()"); rxDcAutoRestore = setRxDcAuto(false) ? 1 : 0; } BRF_TX_SERIALIZE; BRF_CHECK_DEV("setDcOffset()"); 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_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; BRF_CHECK_DEV("getDcOffset()"); unsigned int status = internalGetDcOffset(tx,i,&value); return status; } unsigned int BrfLibUsbDevice::setFpgaCorr(bool tx, int corr, int16_t value) { BRF_TX_SERIALIZE; BRF_CHECK_DEV("setFpgaCorr()"); return internalSetFpgaCorr(tx,corr,value); } unsigned int BrfLibUsbDevice::getFpgaCorr(bool tx, int corr, int16_t& value) { BRF_TX_SERIALIZE; BRF_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; BRF_CHECK_DEV("getTimestamp()"); return internalGetTimestamp(tx,ts); } unsigned int BrfLibUsbDevice::writeLMS(uint8_t addr, uint8_t value, uint8_t* rst) { BRF_TX_SERIALIZE; BRF_CHECK_DEV("writeLMS()"); if (rst) return lmsSet(addr,value,*rst); return lmsWrite(addr,value); } unsigned int BrfLibUsbDevice::setLoopback(const char* name) { 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; BRF_CHECK_DEV("setLoopback()"); return internalSetLoopback(mode); } // Enable or disable loopback unsigned int BrfLibUsbDevice::setLoopback(int mode) { if (!lookup(mode,s_loopback)) { Debug(m_owner,DebugNote,"Unknown loopback mode %d [%p]",mode,m_owner); return RadioInterface::OutOfRange; } BRF_TX_SERIALIZE; BRF_CHECK_DEV("setLoopback()"); return internalSetLoopback(mode); } // Release data void BrfLibUsbDevice::destruct() { doClose(); for (unsigned int i = 0; i < EpCount; i++) m_usbTransfer[i].reset(); GenObject::destruct(); } // Create an interface BrfLibUsbDevice* BrfLibUsbDevice::create(BrfInterface* owner) { if (!s_usbContextInit) { Lock lck(__plugin); if (!s_usbContextInit) { int status = ::libusb_init(0); if (status != LIBUSB_SUCCESS) { String tmp; Debug(&__plugin,DebugNote,"Failed to initialize libusb %s", appendLusbError(tmp,status).c_str()); return 0; } Debug(&__plugin,DebugAll,"Initialized libusb context"); s_usbContextInit = true; lusbSetDebugLevel(); } } return new BrfLibUsbDevice(owner); } 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::HardwareIOError; 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; } unsigned int BrfLibUsbDevice::setStatus(const BrfDevStatus& stat, unsigned int flags, String* error) { unsigned int status = 0; BRF_FUNC_CALL_RET(cancelled(error)); unsigned int tmp = 0; #define SET_STATUS_FUNC(flag,func) \ if ((flags & flag) != 0) { \ tmp = func; \ if (tmp && !status) { \ error = 0; \ status = tmp; \ } \ } XDebug(m_owner,DebugAll,"Set %s status 0x%x [%p]",brfDir(stat.tx()),flags,m_owner); SET_STATUS_FUNC(DevStatLpfBw,internalSetLpfBandwidth(stat.tx(),stat.lpfBw,error)); SET_STATUS_FUNC(DevStatSampleRate,internalSetSampleRate(stat.tx(),stat.sampleRate,error)); SET_STATUS_FUNC(DevStatFreq,internalSetFrequency(stat.tx(),stat.frequency,error)); SET_STATUS_FUNC(DevStatVga1,internalSetVga(stat.tx(),stat.vga1,true,error)); SET_STATUS_FUNC(DevStatVga2,internalSetVga(stat.tx(),stat.vga2,false,error)); SET_STATUS_FUNC(DevStatLpf,internalSetLpf(stat.tx(),stat.lpf,error)); SET_STATUS_FUNC(DevStatDcI,internalSetDcOffset(stat.tx(),true,stat.dcOffsetI,error)); SET_STATUS_FUNC(DevStatDcQ,internalSetDcOffset(stat.tx(),false,stat.dcOffsetQ,error)); SET_STATUS_FUNC(DevStatFpgaPhase,internalSetFpgaCorr(stat.tx(),CorrFpgaPhase, stat.fpgaCorrPhase,error)); SET_STATUS_FUNC(DevStatFpgaGain,internalSetFpgaCorr(stat.tx(),CorrFpgaGain, stat.fpgaCorrGain,error)); SET_STATUS_FUNC(DevStatPowerBalance,internalSetTxIQBalance(false,stat.powerBalance)); return status; } void BrfLibUsbDevice::internalDumpDev(String& buf, bool info, bool state, const char* sep, bool internal, bool fromStatus, bool withHdr) { String tmp; if (state) { if (withHdr) { buf.append("RxVGA1=",sep) << m_rxIO.vga1; buf << sep << "RxVGA2=" << m_rxIO.vga2; buf << sep << "RxDCCorrI=" << m_rxIO.dcOffsetI; buf << sep << "RxDCCorrQ=" << m_rxIO.dcOffsetQ; buf << sep << "TxVGA1=" << m_txIO.vga1; buf << sep << "TxVGA2=" << m_txIO.vga2; buf << sep << dumpFloatG(tmp,(double)m_rxIO.frequency / 1000000,"RxFreq=","MHz"); if (internal) { buf << sep << "TxDCCorrI=" << m_txIO.dcOffsetI; buf << sep << "TxDCCorrQ=" << m_txIO.dcOffsetQ; } buf << sep << dumpFloatG(tmp,(double)m_txIO.frequency / 1000000,"TxFreq=","MHz"); buf << sep << "FreqOffset=" << m_freqOffset; buf << sep << "RxSampRate=" << m_rxIO.sampleRate; buf << sep << "TxSampRate=" << m_txIO.sampleRate; buf << sep << "RxLpfBw=" << m_rxIO.lpfBw; buf << sep << "TxLpfBw=" << m_txIO.lpfBw; buf << sep << "RxRF=" << onStr(m_rxIO.rfEnabled); buf << sep << "TxRF=" << onStr(m_txIO.rfEnabled); if (internal) { buf << sep << "RxLPF=" << lookup(m_rxIO.lpf,s_lpf); buf << sep << "TxLPF=" << lookup(m_txIO.lpf,s_lpf); buf << sep << "TxCorrFpgaPhase=" << m_txIO.fpgaCorrPhase; } } else { buf << "|" << m_rxIO.vga1; buf << "|" << m_rxIO.vga2; buf << "|" << m_rxIO.dcOffsetI; buf << "|" << m_rxIO.dcOffsetQ; buf << "|" << m_txIO.vga1; buf << "|" << m_txIO.vga2; buf << "|" << dumpFloatG(tmp,(double)m_rxIO.frequency / 1000000,0,"MHz"); buf << "|" << dumpFloatG(tmp,(double)m_txIO.frequency / 1000000,0,"MHz"); buf << "|" << m_freqOffset; buf << "|" << m_rxIO.sampleRate; buf << "|" << m_txIO.sampleRate; buf << "|" << m_rxIO.lpfBw; buf << "|" << m_txIO.lpfBw; buf << "|" << onStr(m_rxIO.rfEnabled); buf << "|" << onStr(m_txIO.rfEnabled); } } if (!info) return; if (withHdr) { buf.append("Address=",sep) << "USB/" << bus() << "/" << addr(); buf << sep << "Serial=" << serial(); buf << sep << "Speed=" << speedStr(speed()); buf << sep << "Firmware=" << fwVerStr(); buf << sep << "FPGA=" << fpgaVerStr(); if (!fromStatus) { buf.append(fpgaFile()," - "); buf.append(fpgaMD5()," - MD5: "); } } else { if (buf) buf << "|"; buf << "USB/" << bus() << "/" << addr(); buf << "|" << serial(); buf << "|" << speedStr(speed()); buf << "|" << fwVerStr(); buf << "|" << fpgaVerStr(); } } 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_txIO.rfEnabled) || (rx != m_rxIO.rfEnabled); while (status == 0) { if (tx || rx) BRF_FUNC_CALL_BREAK(enableTimestamps(true,&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; 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 (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; } 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 (io.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_txPatternBufSamples == 0) { brfCopyTxData(start,data,avail,*scaleI,*maxI,*scaleQ,*maxQ,clamped); data += avail * 2; } else sendCopyTxPattern(start,avail,*scaleI,*maxI,*scaleQ,*maxQ,clamped); if (io.crtBufSampOffs >= io.bufSamples) io.advanceBuffer(); } unsigned int nBuf = io.crtBuf; unsigned int oldBufSampOffs = nBuf ? io.crtBufSampOffs : 0; if (m_txIO.syncFlags || m_rxIO.syncFlags) { if ((m_txIO.syncFlags & ~DevStatTs) != 0) setStatus(m_txIO.syncStatus,m_txIO.syncFlags); if ((m_rxIO.syncFlags & ~DevStatTs) != 0) setStatus(m_rxIO.syncStatus,m_rxIO.syncFlags); if ((io.syncFlags & DevStatTs) != 0) io.syncTs = ts + nBuf * io.bufSamples; m_txIO.syncFlags = 0; m_rxIO.syncFlags = 0; } 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.clear(); m_txPatternBufSamples = bytes2samplesf(m_txPattern.length()); if (m_txPatternBufSamples) { // Round up to use full TX buffers unsigned int bufs = (totalSamples(true) + m_txPatternBufSamples - 1) / m_txPatternBufSamples; unsigned int n = samplesf2bytes(m_txPatternBufSamples); while (bufs--) m_txPatternBuffer.append(m_txPattern.data(0),n); m_txPatternBufSamples = bytes2samplesf(m_txPatternBuffer.length()); Debug(m_owner,DebugNote,"Using send pattern %u samples [%p]", m_txPatternBufSamples,m_owner); } else m_txPatternBuffer.clear(); m_txPatternBufPos = m_txPatternBufSamples; } void BrfLibUsbDevice::sendCopyTxPattern(int16_t* buf, unsigned int avail, float scaleI, int16_t maxI, float scaleQ, int16_t maxQ, unsigned int& clamped) { while (avail) { if (m_txPatternBufPos == m_txPatternBufSamples) m_txPatternBufPos = 0; unsigned int cp = m_txPatternBufSamples - m_txPatternBufPos; if (cp > avail) cp = avail; float* b = (float*)m_txPatternBuffer.data(0) + m_txPatternBufPos * 2; avail -= cp; m_txPatternBufPos += cp; brfCopyTxData(buf,b,cp,scaleI,maxI,scaleQ,maxQ,clamped); } } // 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) { #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.checkTs)) ioBufCheckTs(false); if (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 (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 (status != RadioInterface::Cancelled) Debug(m_owner,DebugNote,"Recv failed: %s [%p]",e.c_str(),m_owner); return status; } unsigned int BrfLibUsbDevice::internalSetSampleRate(bool tx, uint32_t value, String* error) { String e; unsigned int status = lusbSetAltInterface(BRF_ALTSET_RF_LINK,&e); 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,DebugGoOn, "Requested sample rate is smaller than the allowed minimum value [%p]", 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 (getIO(tx).sampleRate != value) { getIO(tx).sampleRate = value; Debug(m_owner,DebugInfo,"%s samplerate set to %u [%p]", brfDir(tx),value,m_owner); } 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; } 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 == 0) { uint32_t ver = 0; if (getFpgaVersion(ver) == 0) ver2str(m_devFpgaVerStr,ver); } else Debug(m_owner,DebugWarn,"Failed to load FPGA: %s [%p]",e.c_str(),m_owner); return status; } 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; String what; int a = fpgaCorrAddr(tx,corr,what); int* changed = 0; if (a >= 0) { BrfDevIO& io = getIO(tx); if (corr == CorrFpgaGain) { changed = &io.fpgaCorrGain; orig = clampInt(orig,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX,"FPGA GAIN",lvl); value = orig + BRF_FPGA_CORR_MAX; } else if (corr == CorrFpgaPhase) { changed = (io.showFpgaPhaseChange == 0) ? &io.fpgaCorrPhase : 0; orig = clampInt(orig,-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX,"FPGA PHASE",lvl); value = orig; } status = gpioWrite(a,value,2,&e); } else status = setUnkValue(e,0,"FPGA corr value " + String(corr)); if (status) { e.printf(1024,"Failed to set %s FPGA corr %s to %d (from %d) - %s [%p]", brfDir(tx),what.c_str(),value,orig,e.c_str(),m_owner); return showError(status,e,0,error); } if (changed && *changed != orig) Debug(m_owner,DebugInfo,"%s FPGA corr %s set to %d (from %d) [%p]", brfDir(tx),what.c_str(),value,orig,m_owner); return 0; } unsigned int BrfLibUsbDevice::internalGetFpgaCorr(bool tx, int corr, int16_t* value, String* error) { int16_t v = 0; String e; unsigned int status = 0; String what; int a = fpgaCorrAddr(tx,corr,what); if (a >= 0) { uint32_t u = 0; status = gpioRead(a,u,2,&e); if (status == 0) { v = (int)u; if (corr == CorrFpgaGain) v -= BRF_FPGA_CORR_MAX; if (value) *value = v; } } else status = setUnkValue(e,0,"FPGA corr value " + String(corr)); if (status) { e.printf(1024,"Failed to retrieve %s FPGA corr %s - %s [%p]", brfDir(tx),what.c_str(),e.c_str(),m_owner); return showError(status,e,0,error); } XDebug(m_owner,DebugAll,"Got %s FPGA corr %s %d [%p]", brfDir(tx),what.c_str(),v,m_owner); if (corr == CorrFpgaGain) getIO(tx).fpgaCorrGain = v; else if (corr == CorrFpgaPhase) getIO(tx).fpgaCorrPhase = v; return 0; } 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_txIO.vga1Changed = true; int& old = preMixer ? m_txIO.vga1 : m_txIO.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_txIO.vga1 = v; } else { v = (data >> 3) & 0x1f; if (v > BRF_TXVGA2_GAIN_MAX) v = BRF_TXVGA2_GAIN_MAX; m_txIO.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) == 1; 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_rxIO.vga1 != vga); m_rxIO.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_rxIO.vga2 != vga); m_rxIO.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_rxIO.vga1 = v = s_rxvga1_get[idx < 121 ? idx : 120]; } else { m_rxIO.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_txIO.vga1Changed && m_txIO.vga1 >= BRF_TXVGA1_GAIN_MIN) ? m_txIO.vga1 : BRF_TXVGA1_GAIN_DEF; val = clampInt(val + BRF_TXVGA2_GAIN_MAX,BRF_TXVGA2_GAIN_MIN,BRF_TXVGA2_GAIN_MAX); } else { vga1 = m_rxIO.vga1 > BRF_RXVGA1_GAIN_MAX ? BRF_RXVGA1_GAIN_MAX : m_rxIO.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_txIO.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(int val, int* newVal, String* error) { val = clampInt(val,BRF_FREQ_OFFS_MIN,BRF_FREQ_OFFS_MAX,"FrequencyOffset"); String e; unsigned int status = gpioWrite(0x22,(val & 0xff) << 8,2,&e); if (status == 0) { if (m_freqOffset != val) { Debug(m_owner,DebugInfo,"FrequencyOffset set to %d [%p]",val,m_owner); m_freqOffset = val; } if (newVal) *newVal = val; return 0; } return showError(status,e,"FrequencyOffset set failed",error); } unsigned int BrfLibUsbDevice::internalSetFrequency(bool tx, uint32_t hz, String* error) { XDebug(m_owner,DebugAll,"BrfLibUsbDevice::setFrequency(%u,%s) [%p]", hz,brfDir(tx),m_owner); String e; unsigned int status = 0; BrfDevTmpAltSet tmpAltSet(this,status,&e,"frequency set"); while (!status) { status = RadioInterface::Failure; uint8_t addr = lmsFreqAddr(tx); clampFrequency(hz,tx,"setFrequency"); 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_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 %uHz - %s",brfDir(tx),hz,e.c_str()); return showError(status,e,0,error); } if (getIO(tx).frequency != hz) { getIO(tx).frequency = hz; Debug(m_owner,DebugInfo,"%s frequency set to %gMHz offset=%u [%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) { getIO(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_bulkTout) && 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",DebugGoOn); 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,DebugGoOn,"GPIO write: unhandled speed [%p]",m_owner); } len = clampInt(len,1,sizeof(value),"GPIO write items",DebugGoOn); 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, String* error) { if (!str) return 0; String e; unsigned int status = 0; while (true) { DataBlock db; if (!db.unHexify(str)) { status = setError(RadioInterface::Failure,&e,"Invalid hex string"); break; } if ((db.length() % 2) != 0) { status = setError(RadioInterface::Failure,&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) return 0; break; } e.printf(1024,"LMS write '%s' failed - %s",str.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 [%p]",enabledStr(on),m_owner); return 0; } e.printf("Failed to %s LNA - %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)); unsigned int i = 0; for (; i < 15 && band > s_bandSet[i]; i++) ; uint8_t bw = (uint8_t)(15 - i); data &= ~0x3c; // Clear out previous bandwidth setting data |= (bw << 2); // Apply new bandwidth setting BRF_FUNC_CALL_BREAK(lmsWrite(reg,data,&e)); getIO(tx).lpfBw = s_bandSet[i]; Debug(m_owner,DebugAll,"%s LPF bandwidth set to %u (from %u, reg=0x%x) [%p]", brfDir(tx),getIO(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 (getIO(tx).lpf != lpf) { getIO(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) { getIO(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 BrfDevIO& io = getIO(tx); unsigned int status = 0; String e; if (!m_devHandle) { if (!on) { io.setRf(false); return 0; } status = RadioInterface::NotInitialized; e = "Not open"; } while (!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) break; // Samples circulation 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; status = lusbCtrlTransfer(LUSB_CTRLTRANS_IFACE_VENDOR_IN, request,value,0,(uint8_t*)&buf,sizeof(buf),&e); if (status == 0 && le32toh(buf)) status = setError(RadioInterface::Failure,&e,"Device failure"); if (status) e = "Samples circulation change failed - " + e; break; } if (io.rfEnabled == on) { io.setRf(on && status == 0); return status; } io.setRf(on && status == 0); 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); } // Read the FPGA version unsigned int BrfLibUsbDevice::getFpgaVersion(uint32_t& version) { #ifdef DEBUGGER_DEVICE_METH Debugger d(DebugAll,"BrfLibUsbDevice::getFpgaVersion()"); #endif BRF_CHECK_DEV("getFpgaVersion()"); String e; unsigned int status = 0; BrfDevTmpAltSet tmpAltSet(this,status,&e,"FPGA version get"); if (!status) status = gpioRead(0x0c,version,4,&e); if (status) Debug(m_owner,DebugNote,"Failed to retrieve FPGA version: %s [%p]", e.c_str(),m_owner); return status; } // 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; status = lmsWrite(0x47,0x40,&e,"Could not set the bias current for the LO"); if (status) break; status = lmsWrite(0x59,0x09,&e,"Could not set the ADC"); if (status) break; status = lmsWrite(0x64,0x36,&e,"Could not set the common mode for the ADC"); if (status) break; status = lmsWrite(0x79,0x37,&e,"Could not set the LNA gain"); 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); } // 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) && m_owner->debugAt(DebugAll))) return 0; String s; if (!uartDev.haveTrackAddr()) 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 if (uartDev.isTrackRange(addr,len)) { uint8_t a = addr; for (unsigned int i = 0; i < len && a < 256; i++, a++) if (uartDev.isTrackAddr(a)) { String tmp; s.append(tmp.printf("(0x%x=0x%x)",a,data[i])," "); } if (s) Debug(m_owner,DebugAll,"%s %s %s [%p]", uartDev.c_str(),brfDir(tx),s.c_str(),m_owner); } return 0; } unsigned int BrfLibUsbDevice::internalSetDcOffset(bool tx, bool i, int16_t value, String* error) { int& old = i ? getIO(tx).dcOffsetI : getIO(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 (getIO(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 ? getIO(tx).dcOffsetI : getIO(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; } } 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 BRF_FUNC_CALL(internalGetFrequency(true)); 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); } // Clamp frequency value void BrfLibUsbDevice::clampFrequency(uint32_t& val, bool tx, const char* loc) { if (val >= BRF_FREQUENCY_MIN && val <= BRF_FREQUENCY_MAX) return; uint32_t c = val < BRF_FREQUENCY_MIN ? BRF_FREQUENCY_MIN : BRF_FREQUENCY_MAX; Debug(m_owner,DebugNote,"%s: clamping %s frequency %u to %u [%p]", loc,brfDir(tx),val,c,m_owner); val = c; } int BrfLibUsbDevice::clampInt(int val, int minVal, int maxVal, const char* what, int level) { if (val >= minVal && val <= maxVal) return val; int c = val < minVal ? minVal : maxVal; if (what) Debug(m_owner,level,"Clamping %s %d -> %d [%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; unsigned int failedDesc = 0; libusb_device_descriptor desc; for (unsigned int i = 0; !m_dev && i < m_listCount; i++) { if (::libusb_get_device_descriptor(m_list[i],&desc)) { failedDesc++; continue; } if (desc.idVendor == 7504 && desc.idProduct == 24678) m_dev = m_list[i]; } if (!m_dev) { String e = "No device found"; if (failedDesc) e << " (failed to retrieve " << failedDesc << " device descriptor(s))"; return setError(RadioInterface::NotInitialized,error,e); } 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); status = lusbCheckSuccess(::libusb_open(m_dev,&m_devHandle), error,"Failed to open the libusb device "); if (status) return status; getDevStrDesc(m_devSerial,desc.iSerialNumber,"serial number"); getDevStrDesc(m_devFwVerStr,4,"firmware version"); if (claim) status = lusbCheckSuccess(::libusb_claim_interface(m_devHandle,0), error,"Failed to claim the interface "); return status; } void BrfLibUsbDevice::closeDevice() { if (!m_devHandle) return; m_closingDevice = true; internalPowerOn(false,false,false); m_closingDevice = false; ::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_txIO.dataDumpFile.terminate(owner()); m_txIO.upDumpFile.terminate(owner()); m_rxIO.dataDumpFile.terminate(owner()); m_rxIO.upDumpFile.terminate(owner()); Debug(m_owner,DebugAll,"Device closed [%p]",m_owner); } 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,DebugInfo,"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) { 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) { unsigned int brfBufSamples = 508; unsigned int nBuffers = 4; if (speed() == LIBUSB_SPEED_HIGH) { brfBufSamples = 252; nBuffers = 8; } m_txIO.resetSamplesBuffer(brfBufSamples,16,nBuffers); if (m_minBufsSend > nBuffers) m_minBufsSend = nBuffers; m_rxIO.resetSamplesBuffer(brfBufSamples,16,nBuffers); m_minBufsSend = clampIntParam(params,"tx_min_buffers",nBuffers,1,nBuffers); 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_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_ctrlTransferPage = 64; } return 0; } m_minBufsSend = 1; m_radioCaps.rxLatency = 0; m_radioCaps.txLatency = 0; m_ctrlTransferPage = 0; String e; e << "Unsupported USB speed " << m_devSpeed; return setError(RadioInterface::OutOfRange,error,e); } // Check timestamps after reading from device 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; } for (; i < nBufs; i++) { uint64_t crt = io.bufTs(i); if ((io.lastTs + io.bufSamples) != crt) { if (!invalid) invalid << ": invalid timestamps (buf=ts/delta)"; invalid << " " << (i + 1) << "=" << crt << "/" << (int64_t)(crt - io.lastTs); } io.lastTs = crt; } if (invalid) Debug(m_owner,invalid ? DebugMild : DebugAll,"%s buf_samples=%u: %u buffers%s [%p]", brfDir(tx),io.bufSamples,nBufs,invalid.safe(),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(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::calibrateAuto(String* error) { #ifdef DEBUG_DEVICE_AUTOCAL Debugger debug(DebugAll,"AUTOCALIBRATION"," '%s' [%p]",m_owner->debugName(),m_owner); #endif Debug(m_owner,DebugInfo,"Autocalibration starting ... [%p]",m_owner); String e; unsigned int status = internalSetDcCorr(0,0,0,0,&e); int8_t calVal[BRF_CALIBRATE_LAST][BRF_CALIBRATE_MAX_SUBMODULES]; ::memset(calVal,-1,sizeof(calVal)); BrfDuration duration; for (int m = BRF_CALIBRATE_FIRST; !status && m <= BRF_CALIBRATE_LAST; m++) { if (m == BRF_CALIBRATE_LPF_BANDWIDTH) { DDebug(m_owner,DebugStub,"%s auto calibration not implemented [%p]", calModName(m),m_owner); continue; } 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; 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); } duration.stop(); if (status) { e = "Autocalibration failed - " + e; return showError(status,e,0,error); } String s; #ifdef DEBUG for (int m = BRF_CALIBRATE_FIRST; m <= BRF_CALIBRATE_LAST; m++) { if (m == BRF_CALIBRATE_LPF_BANDWIDTH) continue; 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,"Autocalibration finished in %s [%p]%s", duration.secStr(),m_owner,encloseDashes(s)); return 0; } 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; } 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) { 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)); } // 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(lmsReset(0x3f,0x80,&e)); } else { BRF_FUNC_CALL_BREAK(lmsSet(0x3f,0x80,&e)); BRF_FUNC_CALL_BREAK(lmsReset(0x36,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); } 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[] = {0x35,0x55}; 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; } // Read data, ignore any buffer related errors unsigned int BrfLibUsbDevice::dummyRead(float* buf, unsigned int samples, String* error) { int oldTsCheck = m_rxIO.checkTs; m_rxIO.checkTs = 0; unsigned int status = syncTxStatus(DevStatTs,0,error); if (!status) status = readBuffer(m_txIO.syncTs + 3 * samples,buf,samples,error); m_rxIO.checkTs = oldTsCheck; return status; } unsigned int BrfLibUsbDevice::readBuffer(uint64_t ts, float* buf, unsigned int samples, String* error) { unsigned int status = 0; while (samples) { unsigned int n = samples; BRF_FUNC_CALL_RET(recv(ts,buf,n)); BRF_FUNC_CALL_RET(cancelled(error)); ts += n; buf += n * 2; samples -= n; } return 0; } static inline void cxMult(const float i1, const float q1, const float i2, const float q2, float &ir, float &qr) { ir = i1 * i2 - q1 * q2; qr = i1 * q2 + i2 * q1; } static void calculateDcOffsets(BrfLibUsbDevice* dev, float* input, unsigned samples, float& totalPower, float& rxDcOffset, float* txDcOffset = 0) { if ((samples % 4) != 0) Debug(dev->owner(),DebugFail,"Buffer samples (%u) should be multiple of 4 [%p]", samples,dev->owner()); // one cycle of a -pi/4 complex sinusoid // note that is NOT the same as the test tone cycle array static const float ci[4] = {+1,0,-1,0}; static const float cq[4] = {0,-1,0,+1}; float sumE = 0.0F; float sumRxI = 0.0F; float sumRxQ = 0.0F; float sumTxI = 0.0F; float sumTxQ = 0.0F; float* ip = input; // note that i counts complex pairs, not floats for (unsigned i = 0; i < samples; i++) { const float vi = *ip++; const float vq = *ip++; sumE += vi * vi + vq * vq; sumRxI += vi; sumRxQ += vq; float si, sq; cxMult(vi,vq,ci[i % 4],cq[i % 4],si,sq); sumTxI += si; sumTxQ += sq; } totalPower = sumE / samples; float meanRxI = sumRxI / samples; float meanRxQ = sumRxQ / samples; rxDcOffset = meanRxI * meanRxI + meanRxQ * meanRxQ; if (txDcOffset) { float meanTxI = sumTxI / samples; float meanTxQ = sumTxQ / samples; *txDcOffset = meanTxI * meanTxI+meanTxQ * meanTxQ; } } unsigned int BrfLibUsbDevice::readComputeDcOffsets(uint8_t dcI, uint8_t dcQ, float* buf, unsigned int samples, String* error, float& totalPower, float& rxDcOffset, float& txDcOffset) { unsigned int status = 0; // Apply I/Q offset m_txIO.syncStatus.dcOffsetI = decodeDCOffs(true,dcI); m_txIO.syncStatus.dcOffsetQ = decodeDCOffs(true,dcQ); BRF_FUNC_CALL_RET(syncTxStatus(DevStatTs | DevStatDc,0,error)); BRF_FUNC_CALL_RET(readBuffer(m_txIO.syncTs + 3 * samples,buf,samples,error)); calculateDcOffsets(this,buf,samples,totalPower,rxDcOffset,&txDcOffset); return 0; } unsigned int BrfLibUsbDevice::readComputeDcOffsetsCorr(int* corr, float* powerBalance, float* buf, unsigned int samples, String* error, float& totalPower, float& rxDcOffset) { unsigned int status = 0; if (corr) { // Apply TX FPGA phase correction m_txIO.syncStatus.fpgaCorrPhase = *corr; BRF_FUNC_CALL_RET(syncTxStatus(DevStatTs | DevStatFpgaPhase,0,error)); } else if (powerBalance) { // Apply TX power balance m_txIO.syncStatus.powerBalance = *powerBalance; BRF_FUNC_CALL_RET(syncTxStatus(DevStatTs | DevStatPowerBalance,0,error)); } else return showError(RadioInterface::OutOfRange, "readComputeDcOffsetsCorr: invalid params",0,0,DebugFail); BRF_FUNC_CALL_RET(readBuffer(m_txIO.syncTs + 3 * samples,buf,samples,error)); calculateDcOffsets(this,buf,samples,totalPower,rxDcOffset); return 0; } void BrfLibUsbDevice::calibrateBBStarting(const char* what) { String s; #if 0 internalDumpDev(s,false,true,"\r\n",true,false,true); String dev; internalDumpPeripheral(UartDevLMS,0,128,&dev,16); s << "\r\nLMS:" << dev; dev = ""; internalDumpPeripheral(UartDevGPIO,0,128,&dev,16); s << "\r\nGPIO:" << dev; dev = ""; internalDumpPeripheral(UartDevSI5338,0,128,&dev,16); s << "\r\nSI5338:" << dev; #endif Debug(m_owner,DebugInfo,"%s starting [%p]%s",what,m_owner,encloseDashes(s,true)); } // Utility used in BB calibration static inline void updateBBResult(int8_t& dir, float& last, float crt, bool first) { if (first || last == crt) dir = 0; else dir = (last > crt) ? -1 : 1; last = crt; } static inline const char* dirStr(int8_t dir) { return dir ? (dir > 0 ? "up" : "down") : "="; } struct BBDirChg { inline BBDirChg(uint8_t maxChg) { ::memset(this,0,sizeof(*this)); dirNotChgMax = maxChg; } inline bool update(uint8_t dir) { if (lastDir != dir) { checkDirChg = true; lastDir = dir; dirNotChg = 1; } else dirNotChg++; return !(checkDirChg && dirNotChgMax && dirNotChg >= dirNotChgMax); } int8_t lastDir; bool checkDirChg; uint8_t dirNotChg; uint8_t dirNotChgMax; }; unsigned int BrfLibUsbDevice::calibrateBBTxDc(int& dcI, int& dcQ, float* buf, unsigned int samples, String* error) { //#define BRF_BB_TX_DC_TRACE BrfDuration duration; const char* oper = "Baseband TX DC calibration"; calibrateBBStarting(oper); dcI = 128; dcQ = 128; int tmpDcI = 0; int tmpDcQ = 0; float power = 0; float rxDc = 0; float txDc = 0; uint8_t innerLoops[] = {18,18,31,31,5,5}; uint8_t steps[] = {15,15,1,1,1,1}; uint8_t dirNotChgStop[] = {4,4,5,5,2,2}; String e; m_txIO.showDcOffsChange++; unsigned int status = dummyRead(buf,samples,&e); if (!status) status = readComputeDcOffsets(dcI,dcQ,buf,samples,&e,power,rxDc,txDc); float bestDcOffs = txDc; float lastDcOffs = txDc; #ifdef BRF_BB_TX_DC_TRACE Output("Starting with I/Q=%d/%d power=%g rxDC=%g txDC=%g",dcI,dcQ,power,rxDc,txDc); #endif for (unsigned int i = 0; !status && i < BRF_ARRAY_LEN(innerLoops); i++) { bool iLoop = ((i % 2) == 0); #define SET_I_Q_TEMP(_i,_q) tmpDcI = clampInt(_i,0,255); tmpDcQ = clampInt(_q,0,255); break switch (i) { case 0: SET_I_Q_TEMP(255,128); case 1: SET_I_Q_TEMP(dcI,255); case 2: SET_I_Q_TEMP(dcI + 15,dcQ); case 3: SET_I_Q_TEMP(dcI,dcQ + 15); case 4: SET_I_Q_TEMP(dcI + 2,dcQ); case 5: SET_I_Q_TEMP(dcI,dcQ + 2); } #undef SET_I_Q_TEMP int& bestIQ = iLoop ? dcI : dcQ; int& iq = iLoop ? tmpDcI : tmpDcQ; #ifdef BRF_BB_TX_DC_TRACE Output("%d: LoopMode=%c I=%d Q=%d (best_I/Q=%d/%d)", i + 1,brfIQ(iLoop),tmpDcI,tmpDcQ,dcI,dcQ); #endif BBDirChg dirChg(dirNotChgStop[i]); for (unsigned int n = 0; n < innerLoops[i]; n++) { status = readComputeDcOffsets(tmpDcI,tmpDcQ,buf,samples,&e,power,rxDc,txDc); if (status) break; int8_t dir = 0; updateBBResult(dir,lastDcOffs,txDc,n == 0); // First loop set dir to 'equal', avoid direction change in second loop if (n == 1) dirChg.lastDir = dir; #ifdef BRF_BB_TX_DC_TRACE String dump; dump.printf(1024," I=%-3d Q=%-3d power=%.6f\tdiff=%.6f\trxDC=%.6f\ttxDC=%f\t%s", tmpDcI,tmpDcQ,power,power - (rxDc + txDc),rxDc,txDc,dirStr(dir)); if (bestDcOffs > txDc) dump << "\t" << brfIQ(iLoop) << " " << bestIQ << " -> " << iq; Output("%s",dump.c_str()); #endif if (bestDcOffs > txDc) { bestDcOffs = txDc; bestIQ = iq; } if (!dirChg.update(dir)) break; if (iq - steps[i] < 0) break; iq -= steps[i]; } } duration.stop(); m_txIO.showDcOffsChange--; if (status == 0) { dcI = decodeDCOffs(true,dcI); dcQ = decodeDCOffs(true,dcQ); Debug(m_owner,DebugInfo,"%s finished in %s I=%d Q=%d [%p]", oper,duration.secStr(),dcI,dcQ,m_owner); return 0; } e.printf(2048,"%s failed - %s",oper,e.c_str()); return showError(status,e,0,error); } unsigned int BrfLibUsbDevice::calibrateBBTxPhase(bool bruteForce, int& corr, float* buf, unsigned int samples, String* error) { //#define BRF_BB_PHASE_TRACE BrfDuration duration; const char* oper = "Baseband TX PHASE calibration"; calibrateBBStarting(oper); m_txIO.showFpgaPhaseChange++; corr = 0; float power = 0; float rxDc = 0; String e; unsigned int status = dummyRead(buf,samples,&e); if (bruteForce) { float imagePower = (float)0xffffffffffffffff; #ifdef BRF_BB_PHASE_TRACE Output("Starting BRUTE FORCE image=%g",imagePower); unsigned int n = 0; #endif int i = status ? (BRF_FPGA_CORR_MAX + 1) : -BRF_FPGA_CORR_MAX; for (; i <= BRF_FPGA_CORR_MAX; i++) { status = readComputeDcOffsetsCorr(&i,0,buf,samples,&e,power,rxDc); if (status) break; float image = power - rxDc; #ifdef BRF_BB_PHASE_TRACE n++; if (n < 10) Output(" %-5d\tbest=%d\tpower=%.6f\trxDC=%.6f\timage=%.6f",i,corr,power,rxDc,image); else if ((n % 500) == 0) Output(" %-5d\tbest=%d",i,corr); #endif if (imagePower <= image) continue; imagePower = image; corr = i; } } else { if (!status) status = readComputeDcOffsetsCorr(&corr,0,buf,samples,&e,power,rxDc); float imagePower = power - rxDc; float lastImage = imagePower; #if 0 unsigned int innerLoops[] = {BRF_FPGA_CORR_MAX,200,10}; unsigned int steps[] = {200,10,1}; uint8_t dirNotChgStop[] = {20,0,0}; #else unsigned int innerLoops[] = {BRF_FPGA_CORR_MAX,99}; unsigned int steps[] = {50,1}; uint8_t dirNotChgStop[] = {50,0}; #endif #ifdef BRF_BB_PHASE_TRACE Output("Starting with power=%g rxDC=%g image=%g",power,rxDc,imagePower); #endif for (unsigned int i = 0; !status && i < BRF_ARRAY_LEN(innerLoops); i++) { int v = clampInt(corr + innerLoops[i],-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX); int minV = clampInt(corr - innerLoops[i],-BRF_FPGA_CORR_MAX,BRF_FPGA_CORR_MAX); #ifdef BRF_BB_PHASE_TRACE Output("Loop %u: best=%d interval=[%d..%d] step=%u",i,corr,minV,v,steps[i]); #endif BBDirChg dirChg(dirNotChgStop[i]); unsigned int nMax = innerLoops[i] * 2 + 1; for (unsigned int n = 0; n < nMax; n++) { status = readComputeDcOffsetsCorr(&v,0,buf,samples,&e,power,rxDc); if (status) break; float image = power - rxDc; int8_t dir = 0; updateBBResult(dir,lastImage,image,n == 0); // First loop set dir to 'equal', avoid direction change in second loop if (n == 1) dirChg.lastDir = dir; #ifdef BRF_BB_PHASE_TRACE String dump; dump.printf(" %-5d\tpower=%.6f\trxDC=%.6f\timage=%.6f\t%s", v,power,rxDc,image,dirStr(dir)); if (imagePower > image) dump << "\t" << corr << " -> " << v; Output("%s",dump.c_str()); #endif if (imagePower > image) { imagePower = image; corr = v; } if (!dirChg.update(dir)) break; int tmp = v - steps[i]; if (tmp >= minV) v = tmp; else if ((minV - tmp) < (int)steps[i]) v = minV; else break; } } } duration.stop(); m_txIO.showFpgaPhaseChange--; if (status == 0) { Debug(m_owner,DebugInfo,"%s finished in %s corr=%d [%p]", oper,duration.secStr(),corr,m_owner); return 0; } e.printf(2048,"%s failed - %s",oper,e.c_str()); return showError(status,e,0,error); } unsigned int BrfLibUsbDevice::calibrateBBTxGain(bool bruteForce, float& corr, float* buf, unsigned int samples, String* error) { //#define BRF_BB_GAIN_TRACE BrfDuration duration; const char* oper = "Baseband TX GAIN calibration"; calibrateBBStarting(oper); m_txIO.showPowerBalanceChange++; corr = 1; float power = 0; float rxDc = 0; String e; unsigned int status = dummyRead(buf,samples,&e); if (bruteForce) { // Brute force float imagePower = (float)0xffffffffffffffff; float crt = 0.9; float step = 0.0005; #ifdef BRF_BB_GAIN_TRACE Output("Starting BRUTE FORCE start=%g step=%g image=%g",crt,step,imagePower); unsigned int n = 0; #endif if (status) crt = 3; for (; crt < 1.1; crt += step) { status = readComputeDcOffsetsCorr(0,&crt,buf,samples,&e,power,rxDc); if (status) break; float image = power - rxDc; #ifdef BRF_BB_GAIN_TRACE n++; if (n < 10) Output(" %g\t\tbest=%g\tpower=%.6f\trxDC=%.6f\timage=%.6f",crt,corr,power,rxDc,image); else if ((n % 30) == 0) Output(" %g\tbest=%g",crt,corr); #endif if (imagePower <= image) continue; imagePower = image; corr = crt; } } else { if (!status) status = readComputeDcOffsetsCorr(0,&corr,buf,samples,&e,power,rxDc); float imagePower = power - rxDc; float lastImage = imagePower; #if 0 float innerBounds[] = {1,0.5,0.2,0.1,0.05}; unsigned int innerIntervals[] = {10,20,20,20,100}; #else float innerBounds[] = {0.1,0.01}; unsigned int innerIntervals[] = {500,100}; #endif #ifdef BRF_BB_GAIN_TRACE Output("Starting with power=%g rxDC=%g image=%g",power,rxDc,imagePower); #endif for (unsigned int i = 0; !status && i < BRF_ARRAY_LEN(innerBounds); i++) { float crt = corr + innerBounds[i]; if (crt >= 2) crt = 1.999999; unsigned int intervals = innerIntervals[i] + 1; float step = innerBounds[i] * 2 / intervals; #ifdef BRF_BB_GAIN_TRACE Output("Loop %u: best=%g corrStart=%g intervals=%u step=%.5f",i,corr,crt,intervals,step); #endif BBDirChg dirChg(0); for (unsigned int n = 0; n < intervals && crt > 0; n++, crt -= step) { status = readComputeDcOffsetsCorr(0,&crt,buf,samples,&e,power,rxDc); if (status) break; float image = power - rxDc; int8_t dir = 0; updateBBResult(dir,lastImage,image,n == 0); #ifdef BRF_BB_GAIN_TRACE String dump; dump.printf(" %.6f\tpower=%.6f\trxDC=%.6f\timage=%.6f\t%s", crt,power,rxDc,image,dirStr(dir)); if (imagePower > image) dump << "\t" << corr << " -> " << crt; Output("%s",dump.c_str()); #endif if (imagePower > image) { imagePower = image; corr = crt; } } } } duration.stop(); m_txIO.showPowerBalanceChange--; if (status == 0) { Debug(m_owner,DebugInfo,"%s finished in %s corr=%g [%p]", oper,duration.secStr(),corr,m_owner); return 0; } e.printf(2048,"%s failed - %s",oper,e.c_str()); return showError(status,e,0,error); } unsigned int BrfLibUsbDevice::calibrateBB(String* error) { Debug(m_owner,DebugInfo,"Calibrating BB TX [%p]",m_owner); String e; unsigned int status = 0; String pattern = m_txPatternStr; int oldLoop = m_loopback; BrfDevStatus txOld(m_txIO); BrfDevStatus rxOld(m_rxIO); bool oldRxDcAuto = setRxDcAuto(false); BrfDevTmpAltSet tmpAltSet(this,status,&e,"Calibrate BB"); BrfDuration duration; while (status == 0) { bool paOn = false; bool lowBand = brfIsLowBand(txOld.frequency); int lp = lowBand ? LoopRfLna1 : LoopRfLna2; const char* pattern = "circle"; const char* lmsWr = 0; bool twice = true; bool bruteForce = true; unsigned int samples = m_rxIO.bufSamples * m_rxIO.buffers; NamedList p("calibrate-bb"); loadCfg(false,&p); if (p.c_str()) { lp = lookup(p["loopback"],s_loopback,lp); paOn = p.getBoolValue("transmit"); pattern = p.getValue("txpattern",pattern); lmsWr = p.getValue("lms-write"); twice = p.getBoolValue("phase-gain-twice",true); bruteForce = p.getBoolValue("phase-gain-bruteforce",true); samples = p.getIntValue("samples",samples,512); } if ((samples % 4) != 0) samples = 4 * (samples + 3) / 4; BRF_FUNC_CALL_BREAK(internalSetLoopback(lp,&e)); // Set VGA BRF_FUNC_CALL_BREAK(internalSetVga(true,-14,true,&e)); BRF_FUNC_CALL_BREAK(internalSetVga(true,25,false,&e)); BRF_FUNC_CALL_BREAK(internalSetVga(false,30,true,&e)); BRF_FUNC_CALL_BREAK(internalSetVga(false,0,false,&e)); // 0x64: VCM (bits 5-2): RX VGA2 output common voltage control // Make sure we have the correct value to operate BRF_FUNC_CALL_BREAK(lmsSet(0x64,0x34,0x3c,&e)); if (paOn) BRF_FUNC_CALL_BREAK(selectPaLna(true,lowBand,&e)); setTxPattern(pattern); if (lmsWr) lmsWrite(lmsWr); //dumpLoopbackStatus(); //dumpLmsModulesStatus(); //internalDumpPeripheral(UartDevLMS,0,128,0,16); // Start sending thread m_sendThread = (new BrfThread(this))->start(); if (!m_sendThread) { status = RadioInterface::Failure; e << "Failed to start send data thread"; break; } unsigned int Fc = 850000000; m_calibration.assign(""); m_calibration.clearParams(); DataBlock d(0,samplesf2bytes(samples)); float* buf = (float*)d.data(0); int dcI = 0; int dcQ = 0; int phase = 0; float corrBalance = 0; Debug(m_owner,DebugAll,"BB calibration: samples=%u Fc=%u samplerate=%u [%p]", samples,Fc,m_txIO.sampleRate,m_owner); // Calibrate TX DC m_txIO.syncStatus.frequency = Fc + m_txIO.sampleRate / 4; m_rxIO.syncStatus.frequency = Fc; BRF_FUNC_CALL_BREAK(syncTxStatus(DevStatFreq,DevStatFreq,&e)); BRF_FUNC_CALL_BREAK(calibrateBBTxDc(dcI,dcQ,buf,samples,&e)); BRF_FUNC_CALL_BREAK(internalSetCorrectionIQ(true,dcI,dcQ,&e)); // Calibrate TX FPGA PHASE m_txIO.syncStatus.frequency = Fc; m_rxIO.syncStatus.frequency = Fc - m_txIO.sampleRate / 2; BRF_FUNC_CALL_BREAK(syncTxStatus(DevStatFreq,DevStatFreq,&e)); BRF_FUNC_CALL_BREAK(calibrateBBTxPhase(bruteForce,phase,buf,samples,&e)); BRF_FUNC_CALL_BREAK(internalSetFpgaCorr(true,CorrFpgaPhase,phase,&e)); // Calibrate TX power balance BRF_FUNC_CALL_BREAK(calibrateBBTxGain(bruteForce,corrBalance,buf,samples,&e)); BRF_FUNC_CALL_BREAK(internalSetTxIQBalance(false,corrBalance)); if (twice) { BRF_FUNC_CALL_BREAK(calibrateBBTxPhase(bruteForce,phase,buf,samples,&e)); BRF_FUNC_CALL_BREAK(internalSetFpgaCorr(true,CorrFpgaPhase,phase,&e)); // Calibrate TX power balance BRF_FUNC_CALL_BREAK(calibrateBBTxGain(bruteForce,corrBalance,buf,samples,&e)); BRF_FUNC_CALL_BREAK(internalSetTxIQBalance(false,corrBalance)); } m_calibration.assign(String(Fc)); m_calibration.addParam("tx_dc_i",String(dcI)); m_calibration.addParam("tx_dc_q",String(dcQ)); m_calibration.addParam("tx_fpga_corr_phase",String(phase)); m_calibration.addParam("tx_powerbalance",String(corrBalance)); break; } duration.stop(); setRxDcAuto(oldRxDcAuto); Debug(m_owner,DebugAll,"Finalizing BB calibration [%p]",m_owner); BrfThread::cancelThread(m_sendThread,&m_sendThreadMutex,1000,m_owner,m_owner); // Restore loopback internalSetLoopback(oldLoop); // Restore status unsigned int restore = DevStatFreq | DevStatVga | DevStatLpfBw; setStatus(txOld,restore,rxOld,restore); // Restore TX pattern setTxPattern(pattern); if (status == 0) { String tmp; m_calibration.dump(tmp,"\r\n"); Debug(m_owner,DebugInfo,"Calibrated BB 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); } 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_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_rxDcAuto) return; int corrI = computeCorrection(m_rxDcAvgI,m_rxIO.dcOffsetI,dcIAvg,m_rxDcOffsetMax); int corrQ = computeCorrection(m_rxDcAvgQ,m_rxIO.dcOffsetQ,dcQAvg,m_rxDcOffsetMax); if (corrI == m_rxIO.dcOffsetI && corrQ == m_rxIO.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); if (code != RadioInterface::Cancelled) Debug(m_owner,level,"%s [%p]",tmp.c_str(),m_owner); else Debug(m_owner,DebugAll,"%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, String* error) { if (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 = LmsLna2; // 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)); status = enableRfLoopback(true,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(enableRfLoopback(false,e)); BRF_FUNC_CALL_BREAK(lnaEnable(true,&e)); status = restoreFreq(false,&e); break; default: Debug(m_owner,DebugStub,"Loopback: unhandled value %d [%p]",mode,m_owner); status = setUnkValue(e,"mode " + String(mode)); } if (!status) status = setLoopbackPath(mode,e); break; } if (status == 0) { Debug(m_owner,DebugNote,"Loopback changed '%s' -> '%s' [%p]", lookup(m_loopback,s_loopback),what,m_owner); m_loopback = mode; 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; } unsigned int BrfLibUsbDevice::enableRfLoopback(bool on, String& error) { String e; unsigned int status = lmsChangeMask(0x0b,0x01,on,&e); if (status == 0) { Debug(m_owner,DebugAll,"%s RF loopback [%p]",enabledStr(on),m_owner); return 0; } error << "Failed to " << enableStr(on) << " RF loopback -" << e; return status; } void BrfLibUsbDevice::dumpLoopbackStatus() { #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"; } } status = lmsRead(0x0b,data); BRF_LS_RESULT_OPEN("PD[0] (RF loopback)",0x01,0x00); 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 Debug(m_owner,DebugAll,"Loopback switches: [%p]%s",m_owner,encloseDashes(s)); #undef BRF_LS_RESULT #undef BRF_LS_RESULT_OPEN } void BrfLibUsbDevice::dumpLmsModulesStatus() { #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); 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; } // // BrfInterface // BrfInterface::BrfInterface(const char* name) : RadioInterface(name), m_dev(0), m_txFreqCorr(0) { debugChain(&__plugin); Debug(this,DebugAll,"Interface created [%p]",this); } BrfInterface::~BrfInterface() { Debug(this,DebugAll,"Interface destroyed [%p]",this); } BrfLibUsbDevice* BrfInterface::init() { m_dev = BrfLibUsbDevice::create(this); m_radioCaps = &m_dev->capabilities(); Debug(this,DebugAll,"Created device (%p) [%p]",m_dev,this); return m_dev; } unsigned int BrfInterface::initialize(const NamedList& params) { return m_dev->powerOn(); } 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; \ } #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(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 == "calibrate") METH_CALL(calibrate); 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) { 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); if (hz > 0xffffffff) { Debug(this,DebugNote, "Failed to set %s frequency " FMT64U ": out of range [%p]", brfDir(tx),hz,this); return OutOfRange; } uint32_t freq = (uint32_t)hz; unsigned int status = m_dev->setFrequency(freq,tx); if (status) return status; uint32_t tmp = 0; status = m_dev->getFrequency(tmp,tx); if (status) return status; if (tmp != freq) { Debug(this,DebugNote,"Failed to set %s frequency requested=%u read=%u [%p]", brfDir(tx),freq,tmp,this); return NotExact; } return 0; } 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 OutOfRange; unsigned int status = m_dev->enableRxVga(true,preMixer); if (status) return status; status = m_dev->setRxVga(val,preMixer); if (status) return status; int tmp = 0; status = m_dev->getRxVga(tmp,preMixer); if (status) return status; if (tmp == val) return NoError; Debug(this,DebugNote,"Failed to set RX VGA%c requested=%d read=%d [%p]", mixer(preMixer),val,tmp,this); return NotExact; } 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 OutOfRange; 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; } // 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 OutOfRange; 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) { 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& iface, void* dev) { if (!dev) return false; Lock lck(this); for (ObjList* o = m_ifaces.skipNull(); o; o = o->skipNext()) { iface = static_cast(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(Halt); installRelay(Control); installRelay(RadioCreate,"radio.create",gen.getIntValue("priority",90)); } lusbSetDebugLevel(); 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 iface = static_cast(gen); if (!iface) continue; unlock(); iface->reLoad(); iface = 0; lock(); } } unlock(); } bool BrfModule::received(Message& msg, int id) { if (id == RadioCreate) { // Override parameters from received params const String& what = msg[YSTRING("radio_driver")]; if (what && what != YSTRING("bladerf")) return false; BrfInterface* ifc = createIface(msg); if (ifc) msg.setParam(new NamedPointer("interface",ifc,name())); else msg.setParam(YSTRING("error"),"failure"); return ifc != 0; } if (id == Control) { const String& comp = msg[YSTRING("component")]; RefPointer ifc; if (comp == name() || findIface(ifc,comp)) return onCmdControl(ifc,msg); return false; } else if (id == Halt) { NamedList dummy(""); test("stop",dummy); } 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 ifc; if (findIface(ifc,tmp)) return completeStrList(msg.retValue(),partWord,s_ifcCmds); } return Module::commandComplete(msg,partLine,partWord); } BrfInterface* BrfModule::createIface(const 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")]; NamedList* sect = 0; if (profile && profile != YSTRING("general")) sect = s_cfg.getSection(profile); if (sect) { for (const ObjList* o = sect->paramList()->skipNull(); o; o = o->skipNext()) { const NamedString* ns = static_cast(o->get()); p.setParam(ns->name(),*ns); } } // 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)); m_ifaces.append(ifc)->setDelete(false); BrfLibUsbDevice* dev = ifc->init(); lck.drop(); if (dev && dev->open(p)) return ifc; TelEngine::destruct(ifc); return 0; } void BrfModule::completeIfaces(String& dest, const String& partWord) { Lock lck(this); for (ObjList* o = m_ifaces.skipNull(); o; o = o->skipNext()) { RefPointer ifc = static_cast(o->get()); if (ifc) itemComplete(dest,ifc->toString(),partWord); } } bool BrfModule::onCmdControl(BrfInterface* ifc, Message& msg) { static const char* s_help = "\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 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 show [info=status|statistics|timestamps|boardstatus|peripheral] [peripheral=all|list(lms,gpio,vctcxo,si5338)] [addr=] [len=]" "\r\n Verbose output various interface info" "\r\ncontrol bladerf test oper=start|stop|pause|resume|exec" "\r\n Test commands" "\r\ncontrol bladerf help" "\r\n Display control commands help"; const String& cmd = msg[YSTRING("operation")]; // Module commands if (!ifc) { if (cmd == YSTRING("test")) return test(msg[YSTRING("oper")],msg); 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("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); 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 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 ifc = static_cast(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"; 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] [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"); 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(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 module_name test oper={start|stop|.....} params... bool BrfModule::test(const String& cmd, NamedList& list) { static bool s_exec = false; lock(); while (s_exec) { unlock(); Thread::idle(); if (Thread::check(false)) return false; lock(); } s_exec = true; RefPointer crt = m_test; unlock(); bool ok = true; bool start = (cmd == YSTRING("start")); if (start || cmd == YSTRING("stop")) { const String& name = start ? list[YSTRING("name")] : String::empty(); if (start && !name) { s_exec = false; return retParamError(list,"name"); } lock(); m_test = 0; unlock(); // Start / Stop bool haveTest = (crt != 0); if (crt) { crt->stop(); crt = 0; } if (start && !Thread::check(false)) { Lock lck(this); // Reload config loadCfg(); NamedList* sect = s_cfg.getSection(name); ok = (sect != 0); if (ok) { NamedList p(*s_cfg.createSection(sect->getValue("init_section",*sect))); NamedList cmds(*s_cfg.createSection(sect->getValue("cmds_section",*sect))); NamedList devOpen(*s_cfg.createSection(sect->getValue("dev_section","general"))); BrfTest* ifc = new BrfTest(this->name() + "/" + name,p,devOpen,cmds); m_ifaces.append(ifc)->setDelete(false); BrfLibUsbDevice* dev = ifc->init(); lck.drop(); ok = dev && ifc->start(); if (ok) setTest(true,ifc); TelEngine::destruct(ifc); Debug(this,ok ? DebugInfo : DebugNote,"Test '%s' %s", name.c_str(),(ok ? "started" : "failed to start")); } else Debug(this,DebugConf,"Can't test '%s': no config section",name.c_str()); } else if (!start && haveTest) Debug(this,DebugInfo,"Test stopped"); } else { String error; if (crt) { if (cmd == YSTRING("pause")) crt->pause(); else if (cmd == YSTRING("resume")) crt->resume(); else if (cmd == YSTRING("exec")) { const String& c = list[YSTRING("command")]; if (c) ok = crt->execute(c,list[YSTRING("value")],error,true); else { ok = false; error = "Empty command"; } } else { ok = false; error = "Unknown test command"; } } else ok = false; if (error) list.setParam("error",error); } s_exec = false; return ok; } 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.set(tx,rx,params[p.lowCase + "_trackaddr"]); } } 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 ifc = static_cast(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 // // Start this thread. Set destination pointer on success. Delete object on failure BrfThread* BrfThread::start() { if (startup()) return this; Debug(m_iface,DebugNote,"Failed to start worker '%s' [%p]",name(),(void*)m_iface); delete this; return 0; } 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(); unsigned int intervals = (waitMs / Thread::idleMsec()) + 1; 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() { BrfInterface* i = m_iface ? (BrfInterface*)m_iface : (m_device ? m_device->owner() : 0); if (!i) return; Debug(i,DebugAll,"Worker (%p) '%s' started [%p]",this,name(),i); BrfTest* test = YOBJECT(BrfTest,m_iface); if (test) test->run(); else if (m_device) { unsigned int samples = m_device->totalSamples(true); DataBlock buf(0,samplesf2bytes(samples)); float* b = (float*)buf.data(0); uint64_t ts = 0; m_device->internalGetTimestamp(true,ts); Debug(m_device->owner(),DebugAll, "Start sending at ts=" FMT64U " chunk=%u samples [%p]", ts,samples,m_device->owner()); unsigned int status = 0; while (!status) { BRF_FUNC_CALL_BREAK(m_device->send(ts,b,samples)); BRF_FUNC_CALL_BREAK(m_device->cancelled()); ts += samples; } } notify(); } void BrfThread::notify() { RefPointer ifc = m_iface; m_iface = 0; BrfLibUsbDevice* dev = m_device; m_device = 0; if (!(ifc || dev)) return; BrfInterface* i = ifc ? (BrfInterface*)ifc : dev->owner(); bool ok = (m_name == Thread::currentName()); Debug(i,ok ? DebugAll : DebugWarn,"Worker (%p) '%s' terminated [%p]",this,name(),i); BrfTest* test = YOBJECT(BrfTest,ifc); if (test) { if (test->workerTerminated(this)) test->stop(); } else if (dev) { Lock lck(dev->m_sendThreadMutex); if (dev->m_sendThread == this) dev->m_sendThread = 0; } ifc = 0; } // // BrfTest // BrfTest::BrfTest(const char* name, const NamedList& params, const NamedList& devOpen, const NamedList& cmds) : BrfInterface(name), Mutex(false,"BrfTest"), m_state(Idle), m_pause(false), m_pauseSend(false), m_pauseRead(false), m_sendBufCount(0), m_sendOnly(false), m_worker(0), m_sentSamples(0), m_sendTs(0), m_sendBuf(0), m_sendBufSamples(0), m_readTs(0), m_skippedBuffs(0), m_params(params), m_devOpen(devOpen), m_cmds(cmds) { } bool BrfTest::start() { Lock lck(this); if (m_state == Running) return true; if (m_state != Idle) return false; Debug(this,DebugInfo,"Starting ... [%p]",this); String e; while (true) { m_worker = (new BrfThread(this,"BrfTest"))->start(); if (!m_worker) e = "Failed to start worker(s)"; break; } if (e) Debug(this,DebugNote,"Start failure: %s [%p]",e.c_str(),this); else m_state = Running; return m_state == Running; } void BrfTest::stop() { if (device()) device()->exiting(true); __plugin.setTest(false,this); if (m_state == Stopping || m_state == Idle) return; Lock lck(this); if (m_state == Stopping || m_state == Idle) return; m_state = Stopping; Debug(this,DebugInfo,"Stopping ... [%p]",this); lck.drop(); BrfThread::cancelThread(m_worker,this,1000,this,this); lck.acquire(this); m_state = Idle; Debug(this,DebugInfo,"Stopped [%p]",this); } bool BrfTest::execute(const String& cmd, const String& param, String& error, bool fatal, const NamedList* params) { XDebug(this,DebugAll,"execute(%s,%s) [%p]",cmd.c_str(),param.c_str(),this); String e; unsigned int c = RadioInterface::Failure; if (cmd == YSTRING("loopback")) c = device()->setLoopback(param); else if (cmd == YSTRING("samplerate")) c = setSampleRate(param.toInteger()); else if (cmd == YSTRING("filter")) c = setFilter(param.toInteger()); else if (cmd == YSTRING("txfrequency")) c = setFrequency(param.toInteger(),true); else if (cmd == YSTRING("rxfrequency")) c = setFrequency(param.toInteger(),false); else if (cmd == YSTRING("calibrate")) c = device()->calibrate(); else if (cmd == YSTRING("powerbalance")) { if (param) c = device()->setTxIQBalance((float)param.toDouble(-2)); else e = "Missing required parameter"; } else if (cmd == YSTRING("txpattern")) c = device()->setTxPattern(param); else { Debug(this,DebugNote,"Unhandled command '%s' [%p]",cmd.c_str(),this); return true; } if (c == 0 || !fatal) return true; error.printf("'%s' failed with %u '%s'",cmd.c_str(),c,errorName(c)); error.append(e," - "); return false; } bool BrfTest::runInit() { String e; int level = DebugNote; while (true) { m_sendBufCount = m_params.getIntValue("send_count",0,0); m_sendOnly = m_params.getBoolValue("send_only",true); // Open device if (!device()->open(m_devOpen)) { e = "Device open failed"; break; } if (!execute(m_cmds,"init:",e)) break; if (initialize()) { e = "Initialize failure"; break; } if (!execute(m_cmds,"cmd:",e)) break; m_sendBufSamples = m_params.getIntValue("send_samples",0,0,5000); if (!m_sendBufSamples) m_sendBufSamples = device()->bufSamples(true); if (!m_sendBufSamples) { e = "Send buf samples is 0"; break; } m_sendBufData.resize(samplesf2bytes(m_sendBufSamples)); m_sendBuf = (float*)m_sendBufData.data(0); break; } if (e) Debug(this,level,"Init failure: %s [%p]",e.c_str(),this); return e.null(); } bool BrfTest::execute(const NamedList& cmds, const char* prefix, String& error) { for (const ObjList* o = cmds.paramList()->skipNull(); o; o = o->skipNext()) { const NamedString* ns = static_cast(o->get()); String s = ns->name(); if (s.startSkip(prefix,false) && !execute(s,*ns,error,cmds.getBoolValue(s + "_fatal",true),&cmds)) return false; } return true; } void BrfTest::run() { Debug(this,DebugInfo,"Running [%p]",this); if (!runInit()) return; if (m_sendOnly) runSendOnly(); else runSendRecv(); } void BrfTest::runSendOnly() { Debug(this,DebugInfo,"Running send only test (send %u samples) [%p]", m_sendBufSamples,this); m_sendTs = 0; m_sentSamples = 0; while (!Thread::check(false) && write()) ; dumpStats(); } void BrfTest::runSendRecv() { String s; s << "\r\nsend_count=" << m_sendBufCount; Debug(this,DebugInfo,"Running send/recv test [%p]%s",this,encloseDashes(s)); m_sentSamples = 0; // Set RX buf // Read multiple of device RX samples, at least sent samples unsigned int rxSamples = device()->bufSamples(false); if (rxSamples < m_sendBufSamples) { unsigned int rest = m_sendBufSamples % rxSamples; rxSamples = m_sendBufSamples + rxSamples - rest; } resetBufs(rxSamples); while (!Thread::check(false) && write() && read()) { if (m_sendBufCount) { m_sendBufCount--; if (!m_sendBufCount) break; } } dumpStats(); } void BrfTest::dumpStats() { String s; s << "\r\nsent=" << m_sentSamples << " samples"; Debug(this,DebugInfo,"Terminated [%p]%s",this,encloseDashes(s)); } bool BrfTest::workerTerminated(BrfThread* th) { Lock lck(this); if (m_worker == th) { m_worker = 0; return true; } return false; } bool BrfTest::checkPause(bool tx) { bool& paused = tx ? m_pauseSend : m_pauseRead; if (m_pause) { if (!paused) { paused = true; Debug(this,DebugInfo,"%s paused [%p]",brfDir(tx),this); } Thread::idle(); return false; } if (paused) { paused = false; Debug(this,DebugInfo,"%s resumed [%p]",brfDir(tx),this); } return true; } bool BrfTest::write() { if (!checkPause(true)) { if (device()->cancelled()) return false; return true; } if (!m_sendTs) updateTs(true); unsigned int code = device()->syncTx(m_sendTs,m_sendBuf,m_sendBufSamples); if (!code) code = device()->cancelled(); if (!code) { m_sendTs += m_sendBufSamples; m_sentSamples += m_sendBufSamples; } else if (code != Cancelled) Debug(this,DebugNote,"Send error: %u '%s' [%p]",code,errorName(code),this); return code == 0; } bool BrfTest::read() { if (!checkPause(false)) { if (device()->cancelled()) return false; return true; } if (!m_readTs) updateTs(false); m_skippedBuffs = 0; unsigned int code = RadioInterface::read(m_readTs,m_bufs,m_skippedBuffs); if (!code) code = device()->cancelled(); if (code && code != Cancelled) Debug(this,DebugNote,"Device read error: %u '%s' [%p]",code,errorName(code),this); return code == 0; } }; // anonymous namespace /* vi: set ts=8 sw=4 sts=4 noet enc=utf-8: */