mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-trx.git
synced 2025-10-23 08:22:00 +00:00
Transceiver: Pass config struct instead of large list of params
Change-Id: Ifb43cb11f3e7a69b0a88f632f0a0c90ada7f939e
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
* osmo-trx (CXX, dir Transceiver52)
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
enum FillerType {
|
||||
FILLER_DUMMY,
|
||||
FILLER_ZERO,
|
||||
@@ -18,3 +20,38 @@ enum ReferenceType {
|
||||
REF_EXTERNAL,
|
||||
REF_GPS,
|
||||
};
|
||||
|
||||
/* Maximum number of physical RF channels */
|
||||
#define TRX_CHAN_MAX 8
|
||||
|
||||
struct trx_ctx;
|
||||
|
||||
struct trx_chan {
|
||||
struct trx_ctx *trx; /* backpointer */
|
||||
unsigned int idx; /* channel index */
|
||||
char *rx_path;
|
||||
char *tx_path;
|
||||
};
|
||||
|
||||
struct trx_cfg {
|
||||
char *bind_addr;
|
||||
char *remote_addr;
|
||||
char *dev_args;
|
||||
unsigned int base_port;
|
||||
unsigned int tx_sps;
|
||||
unsigned int rx_sps;
|
||||
unsigned int rtsc;
|
||||
unsigned int rach_delay;
|
||||
enum ReferenceType clock_ref;
|
||||
enum FillerType filler;
|
||||
bool multi_arfcn;
|
||||
double offset;
|
||||
double rssi_offset;
|
||||
bool swap_channels;
|
||||
bool ext_rach;
|
||||
bool egprs;
|
||||
unsigned int sched_rr;
|
||||
unsigned int stack_size;
|
||||
unsigned int num_chans;
|
||||
struct trx_chan chans[TRX_CHAN_MAX];
|
||||
};
|
||||
|
@@ -8,8 +8,6 @@ extern struct vty_app_info g_vty_info;
|
||||
extern const struct value_string clock_ref_names[];
|
||||
extern const struct value_string filler_names[];
|
||||
|
||||
/* Maximum number of physical RF channels */
|
||||
#define TRX_CHAN_MAX 8
|
||||
/* Maximum number of carriers in multi-ARFCN mode */
|
||||
#define TRX_MCHAN_MAX 3
|
||||
|
||||
@@ -35,38 +33,8 @@ extern const struct value_string filler_names[];
|
||||
#define DEFAULT_TRX_IP "127.0.0.1"
|
||||
#define DEFAULT_CHANS 1
|
||||
|
||||
struct trx_ctx;
|
||||
|
||||
struct trx_chan {
|
||||
struct trx_ctx *trx; /* backpointer */
|
||||
unsigned int idx; /* channel index */
|
||||
char *rx_path;
|
||||
char *tx_path;
|
||||
};
|
||||
|
||||
struct trx_ctx {
|
||||
struct {
|
||||
char *bind_addr;
|
||||
char *remote_addr;
|
||||
char *dev_args;
|
||||
unsigned int base_port;
|
||||
unsigned int tx_sps;
|
||||
unsigned int rx_sps;
|
||||
unsigned int rtsc;
|
||||
unsigned int rach_delay;
|
||||
enum ReferenceType clock_ref;
|
||||
enum FillerType filler;
|
||||
bool multi_arfcn;
|
||||
double offset;
|
||||
double rssi_offset;
|
||||
bool swap_channels;
|
||||
bool ext_rach;
|
||||
bool egprs;
|
||||
unsigned int sched_rr;
|
||||
unsigned int stack_size;
|
||||
unsigned int num_chans;
|
||||
struct trx_chan chans[TRX_CHAN_MAX];
|
||||
} cfg;
|
||||
struct trx_cfg cfg;
|
||||
};
|
||||
|
||||
int trx_vty_init(struct trx_ctx* trx);
|
||||
|
@@ -132,20 +132,14 @@ bool TransceiverState::init(FillerType filler, size_t sps, float scale, size_t r
|
||||
return false;
|
||||
}
|
||||
|
||||
Transceiver::Transceiver(int wBasePort,
|
||||
const char *TRXAddress,
|
||||
const char *GSMcoreAddress,
|
||||
size_t tx_sps, size_t rx_sps, size_t chans,
|
||||
Transceiver::Transceiver(const struct trx_cfg *cfg,
|
||||
GSM::Time wTransmitLatency,
|
||||
RadioInterface *wRadioInterface,
|
||||
double wRssiOffset, int wStackSize)
|
||||
: mBasePort(wBasePort), mLocalAddr(TRXAddress), mRemoteAddr(GSMcoreAddress),
|
||||
mClockSocket(-1), mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface),
|
||||
rssiOffset(wRssiOffset), stackSize(wStackSize),
|
||||
mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mExtRACH(false), mEdge(false),
|
||||
mOn(false), mForceClockInterface(false),
|
||||
mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), mMaxExpectedDelayNB(0),
|
||||
mWriteBurstToDiskMask(0)
|
||||
RadioInterface *wRadioInterface)
|
||||
: cfg(cfg), mClockSocket(-1),
|
||||
mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface),
|
||||
mChans(cfg->num_chans), mOn(false), mForceClockInterface(false),
|
||||
mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0),
|
||||
mMaxExpectedDelayNB(0), mWriteBurstToDiskMask(0)
|
||||
{
|
||||
txFullScale = mRadioInterface->fullScaleInputValue();
|
||||
rxFullScale = mRadioInterface->fullScaleOutputValue();
|
||||
@@ -199,11 +193,9 @@ int Transceiver::ctrl_sock_cb(struct osmo_fd *bfd, unsigned int flags)
|
||||
* are still expected to report clock indications through control channel
|
||||
* activity.
|
||||
*/
|
||||
bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
|
||||
bool edge, bool ext_rach)
|
||||
bool Transceiver::init()
|
||||
{
|
||||
int d_srcport, d_dstport, c_srcport, c_dstport;
|
||||
|
||||
if (!mChans) {
|
||||
LOG(FATAL) << "No channels assigned";
|
||||
return false;
|
||||
@@ -214,9 +206,6 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
|
||||
return false;
|
||||
}
|
||||
|
||||
mExtRACH = ext_rach;
|
||||
mEdge = edge;
|
||||
|
||||
mDataSockets.resize(mChans, -1);
|
||||
mCtrlSockets.resize(mChans);
|
||||
mTxPriorityQueueServiceLoopThreads.resize(mChans);
|
||||
@@ -228,27 +217,28 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
|
||||
mVersionTRXD.resize(mChans);
|
||||
|
||||
/* Filler table retransmissions - support only on channel 0 */
|
||||
if (filler == FILLER_DUMMY)
|
||||
if (cfg->filler == FILLER_DUMMY)
|
||||
mStates[0].mRetrans = true;
|
||||
|
||||
/* Setup sockets */
|
||||
mClockSocket = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
|
||||
mLocalAddr.c_str(), mBasePort,
|
||||
mRemoteAddr.c_str(), mBasePort + 100,
|
||||
cfg->bind_addr, cfg->base_port,
|
||||
cfg->remote_addr, cfg->base_port + 100,
|
||||
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
|
||||
if (mClockSocket < 0)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < mChans; i++) {
|
||||
int rv;
|
||||
c_srcport = mBasePort + 2 * i + 1;
|
||||
c_dstport = mBasePort + 2 * i + 101;
|
||||
d_srcport = mBasePort + 2 * i + 2;
|
||||
d_dstport = mBasePort + 2 * i + 102;
|
||||
FillerType filler = cfg->filler;
|
||||
c_srcport = cfg->base_port + 2 * i + 1;
|
||||
c_dstport = cfg->base_port + 2 * i + 101;
|
||||
d_srcport = cfg->base_port + 2 * i + 2;
|
||||
d_dstport = cfg->base_port + 2 * i + 102;
|
||||
|
||||
rv = osmo_sock_init2_ofd(&mCtrlSockets[i].conn_bfd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
|
||||
mLocalAddr.c_str(), c_srcport,
|
||||
mRemoteAddr.c_str(), c_dstport,
|
||||
cfg->bind_addr, c_srcport,
|
||||
cfg->remote_addr, c_dstport,
|
||||
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
|
||||
if (rv < 0)
|
||||
return false;
|
||||
@@ -258,8 +248,8 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
|
||||
|
||||
|
||||
mDataSockets[i] = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
|
||||
mLocalAddr.c_str(), d_srcport,
|
||||
mRemoteAddr.c_str(), d_dstport,
|
||||
cfg->bind_addr, d_srcport,
|
||||
cfg->remote_addr, d_dstport,
|
||||
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
|
||||
if (mDataSockets[i] < 0)
|
||||
return false;
|
||||
@@ -267,7 +257,7 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay,
|
||||
if (i && filler == FILLER_DUMMY)
|
||||
filler = FILLER_ZERO;
|
||||
|
||||
mStates[i].init(filler, mSPSTx, txFullScale, rtsc, rach_delay);
|
||||
mStates[i].init(filler, cfg->tx_sps, txFullScale, cfg->rtsc, cfg->rach_delay);
|
||||
}
|
||||
|
||||
/* Randomize the central clock */
|
||||
@@ -309,8 +299,8 @@ bool Transceiver::start()
|
||||
}
|
||||
|
||||
/* Device is running - launch I/O threads */
|
||||
mRxLowerLoopThread = new Thread(stackSize);
|
||||
mTxLowerLoopThread = new Thread(stackSize);
|
||||
mRxLowerLoopThread = new Thread(cfg->stack_size);
|
||||
mTxLowerLoopThread = new Thread(cfg->stack_size);
|
||||
mTxLowerLoopThread->start((void * (*)(void*))
|
||||
TxLowerLoopAdapter,(void*) this);
|
||||
mRxLowerLoopThread->start((void * (*)(void*))
|
||||
@@ -321,14 +311,14 @@ bool Transceiver::start()
|
||||
TrxChanThParams *params = (TrxChanThParams *)malloc(sizeof(struct TrxChanThParams));
|
||||
params->trx = this;
|
||||
params->num = i;
|
||||
mRxServiceLoopThreads[i] = new Thread(stackSize);
|
||||
mRxServiceLoopThreads[i] = new Thread(cfg->stack_size);
|
||||
mRxServiceLoopThreads[i]->start((void * (*)(void*))
|
||||
RxUpperLoopAdapter, (void*) params);
|
||||
|
||||
params = (TrxChanThParams *)malloc(sizeof(struct TrxChanThParams));
|
||||
params->trx = this;
|
||||
params->num = i;
|
||||
mTxPriorityQueueServiceLoopThreads[i] = new Thread(stackSize);
|
||||
mTxPriorityQueueServiceLoopThreads[i] = new Thread(cfg->stack_size);
|
||||
mTxPriorityQueueServiceLoopThreads[i]->start((void * (*)(void*))
|
||||
TxUpperLoopAdapter, (void*) params);
|
||||
}
|
||||
@@ -401,9 +391,9 @@ void Transceiver::addRadioVector(size_t chan, BitVector &bits,
|
||||
|
||||
/* Use the number of bits as the EDGE burst indicator */
|
||||
if (bits.size() == EDGE_BURST_NBITS)
|
||||
burst = modulateEdgeBurst(bits, mSPSTx);
|
||||
burst = modulateEdgeBurst(bits, cfg->tx_sps);
|
||||
else
|
||||
burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), mSPSTx);
|
||||
burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), cfg->tx_sps);
|
||||
|
||||
scaleVector(*burst, txFullScale * pow(10, -RSSI / 10));
|
||||
|
||||
@@ -566,16 +556,16 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime,
|
||||
break;
|
||||
case IV:
|
||||
case VI:
|
||||
return mExtRACH ? EXT_RACH : RACH;
|
||||
return cfg->ext_rach ? EXT_RACH : RACH;
|
||||
break;
|
||||
case V: {
|
||||
int mod51 = burstFN % 51;
|
||||
if ((mod51 <= 36) && (mod51 >= 14))
|
||||
return mExtRACH ? EXT_RACH : RACH;
|
||||
return cfg->ext_rach ? EXT_RACH : RACH;
|
||||
else if ((mod51 == 4) || (mod51 == 5))
|
||||
return mExtRACH ? EXT_RACH : RACH;
|
||||
return cfg->ext_rach ? EXT_RACH : RACH;
|
||||
else if ((mod51 == 45) || (mod51 == 46))
|
||||
return mExtRACH ? EXT_RACH : RACH;
|
||||
return cfg->ext_rach ? EXT_RACH : RACH;
|
||||
else if (mHandover[burstTN][sdcch4_subslot[burstFN % 102]])
|
||||
return RACH;
|
||||
else
|
||||
@@ -593,11 +583,11 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime,
|
||||
case XIII: {
|
||||
int mod52 = burstFN % 52;
|
||||
if ((mod52 == 12) || (mod52 == 38))
|
||||
return mExtRACH ? EXT_RACH : RACH;
|
||||
return cfg->ext_rach ? EXT_RACH : RACH;
|
||||
else if ((mod52 == 25) || (mod52 == 51))
|
||||
return IDLE;
|
||||
else /* Enable 8-PSK burst detection if EDGE is enabled */
|
||||
return mEdge ? EDGE : TSC;
|
||||
return cfg->egprs ? EDGE : TSC;
|
||||
break;
|
||||
}
|
||||
case LOOPBACK:
|
||||
@@ -685,7 +675,7 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
|
||||
|
||||
/* Select the diversity channel with highest energy */
|
||||
for (size_t i = 0; i < radio_burst->chans(); i++) {
|
||||
float pow = energyDetect(*radio_burst->getVector(i), 20 * mSPSRx);
|
||||
float pow = energyDetect(*radio_burst->getVector(i), 20 * cfg->rx_sps);
|
||||
if (pow > max) {
|
||||
max = pow;
|
||||
max_i = i;
|
||||
@@ -710,8 +700,8 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
|
||||
state->mNoiseLev = state->mNoises.avg();
|
||||
}
|
||||
|
||||
bi->rssi = 20.0 * log10(rxFullScale / avg) + rssiOffset;
|
||||
bi->noise = 20.0 * log10(rxFullScale / state->mNoiseLev) + rssiOffset;
|
||||
bi->rssi = 20.0 * log10(rxFullScale / avg) + cfg->rssi_offset;
|
||||
bi->noise = 20.0 * log10(rxFullScale / state->mNoiseLev) + cfg->rssi_offset;
|
||||
|
||||
if (type == IDLE)
|
||||
goto ret_idle;
|
||||
@@ -720,7 +710,7 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
|
||||
mMaxExpectedDelayAB : mMaxExpectedDelayNB;
|
||||
|
||||
/* Detect normal or RACH bursts */
|
||||
rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, mSPSRx, type, max_toa, &ebp);
|
||||
rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, cfg->rx_sps, type, max_toa, &ebp);
|
||||
if (rc <= 0) {
|
||||
if (rc == -SIGERR_CLIP) {
|
||||
LOGCHAN(chan, DTRXDUL, INFO) << "Clipping detected on received RACH or Normal Burst";
|
||||
@@ -738,7 +728,7 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi)
|
||||
bi->toa = ebp.toa;
|
||||
bi->tsc = ebp.tsc;
|
||||
bi->ci = ebp.ci;
|
||||
rxBurst = demodAnyBurst(*burst, mSPSRx, ebp.amp, ebp.toa, type);
|
||||
rxBurst = demodAnyBurst(*burst, cfg->rx_sps, ebp.amp, ebp.toa, type);
|
||||
|
||||
/* EDGE demodulator returns 444 (gSlotLen * 3) bits */
|
||||
if (rxBurst->size() == EDGE_BURST_NBITS) {
|
||||
@@ -1055,8 +1045,8 @@ bool Transceiver::driveTxPriorityQueue(size_t chan)
|
||||
burstLen = gSlotLen;
|
||||
break;
|
||||
case sizeof(*dl) + EDGE_BURST_NBITS: /* EDGE burst */
|
||||
if (mSPSTx != 4) {
|
||||
LOGCHAN(chan, DTRXDDL, ERROR) << "EDGE burst received but SPS is set to " << mSPSTx;
|
||||
if (cfg->tx_sps != 4) {
|
||||
LOGCHAN(chan, DTRXDDL, ERROR) << "EDGE burst received but SPS is set to " << cfg->tx_sps;
|
||||
return false;
|
||||
}
|
||||
burstLen = EDGE_BURST_NBITS;
|
||||
@@ -1165,9 +1155,9 @@ void Transceiver::logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi)
|
||||
|
||||
LOGCHAN(chan, DTRXDUL, DEBUG) << std::fixed << std::right
|
||||
<< " time: " << unsigned(bi->tn) << ":" << bi->fn
|
||||
<< " RSSI: " << std::setw(5) << std::setprecision(1) << (bi->rssi - rssiOffset)
|
||||
<< " RSSI: " << std::setw(5) << std::setprecision(1) << (bi->rssi - cfg->rssi_offset)
|
||||
<< "dBFS/" << std::setw(6) << -bi->rssi << "dBm"
|
||||
<< " noise: " << std::setw(5) << std::setprecision(1) << (bi->noise - rssiOffset)
|
||||
<< " noise: " << std::setw(5) << std::setprecision(1) << (bi->noise - cfg->rssi_offset)
|
||||
<< "dBFS/" << std::setw(6) << -bi->noise << "dBm"
|
||||
<< " TOA: " << std::setw(5) << std::setprecision(2) << bi->toa
|
||||
<< " C/I: " << std::setw(5) << std::setprecision(2) << bi->ci << "dB"
|
||||
|
@@ -100,27 +100,19 @@ struct TransceiverState {
|
||||
class Transceiver {
|
||||
public:
|
||||
/** Transceiver constructor
|
||||
@param wBasePort base port number of UDP sockets
|
||||
@param TRXAddress IP address of the TRX, as a string
|
||||
@param GSMcoreAddress IP address of the GSM core, as a string
|
||||
@param wSPS number of samples per GSM symbol
|
||||
@param cfg VTY populated config
|
||||
@param wTransmitLatency initial setting of transmit latency
|
||||
@param radioInterface associated radioInterface object
|
||||
*/
|
||||
Transceiver(int wBasePort,
|
||||
const char *TRXAddress,
|
||||
const char *GSMcoreAddress,
|
||||
size_t tx_sps, size_t rx_sps, size_t chans,
|
||||
Transceiver(const struct trx_cfg *cfg,
|
||||
GSM::Time wTransmitLatency,
|
||||
RadioInterface *wRadioInterface,
|
||||
double wRssiOffset, int stackSize);
|
||||
RadioInterface *wRadioInterface);
|
||||
|
||||
/** Destructor */
|
||||
~Transceiver();
|
||||
|
||||
/** Start the control loop */
|
||||
bool init(FillerType filler, size_t rtsc, unsigned rach_delay,
|
||||
bool edge, bool ext_rach);
|
||||
bool init(void);
|
||||
|
||||
/** attach the radioInterface receive FIFO */
|
||||
bool receiveFIFO(VectorFIFO *wFIFO, size_t chan)
|
||||
@@ -133,7 +125,7 @@ public:
|
||||
}
|
||||
|
||||
/** accessor for number of channels */
|
||||
size_t numChans() const { return mChans; };
|
||||
size_t numChans() const { return cfg->num_chans; };
|
||||
|
||||
/** Codes for channel combinations */
|
||||
typedef enum {
|
||||
@@ -156,7 +148,6 @@ public:
|
||||
} ChannelCombination;
|
||||
|
||||
private:
|
||||
|
||||
struct ctrl_msg {
|
||||
char data[101];
|
||||
ctrl_msg() {};
|
||||
@@ -177,10 +168,7 @@ struct ctrl_sock_state {
|
||||
}
|
||||
};
|
||||
|
||||
int mBasePort;
|
||||
std::string mLocalAddr;
|
||||
std::string mRemoteAddr;
|
||||
|
||||
const struct trx_cfg *cfg; ///< VTY populated config
|
||||
std::vector<int> mDataSockets; ///< socket for writing to/reading from GSM core
|
||||
std::vector<ctrl_sock_state> mCtrlSockets; ///< socket for writing/reading control commands from GSM core
|
||||
int mClockSocket; ///< socket for writing clock updates to GSM core
|
||||
@@ -202,9 +190,6 @@ struct ctrl_sock_state {
|
||||
double txFullScale; ///< full scale input to radio
|
||||
double rxFullScale; ///< full scale output to radio
|
||||
|
||||
double rssiOffset; ///< RSSI to dBm conversion offset
|
||||
int stackSize; ///< stack size for threads, 0 = OS default
|
||||
|
||||
/** modulate and add a burst to the transmit queue */
|
||||
void addRadioVector(size_t chan, BitVector &bits,
|
||||
int RSSI, GSM::Time &wTime);
|
||||
@@ -233,12 +218,7 @@ struct ctrl_sock_state {
|
||||
/** drive handling of control messages from GSM core */
|
||||
int ctrl_sock_handle_rx(int chan);
|
||||
|
||||
int mSPSTx; ///< number of samples per Tx symbol
|
||||
int mSPSRx; ///< number of samples per Rx symbol
|
||||
size_t mChans;
|
||||
|
||||
bool mExtRACH;
|
||||
bool mEdge;
|
||||
bool mOn; ///< flag to indicate that transceiver is powered on
|
||||
bool mForceClockInterface; ///< flag to indicate whether IND CLOCK shall be sent unconditionally after transceiver is started
|
||||
bool mHandover[8][8]; ///< expect handover to the timeslot/subslot
|
||||
|
@@ -144,12 +144,8 @@ int makeTransceiver(struct trx_ctx *trx, RadioInterface *radio)
|
||||
{
|
||||
VectorFIFO *fifo;
|
||||
|
||||
transceiver = new Transceiver(trx->cfg.base_port, trx->cfg.bind_addr,
|
||||
trx->cfg.remote_addr, trx->cfg.tx_sps,
|
||||
trx->cfg.rx_sps, trx->cfg.num_chans, GSM::Time(3,0),
|
||||
radio, trx->cfg.rssi_offset, trx->cfg.stack_size);
|
||||
if (!transceiver->init(trx->cfg.filler, trx->cfg.rtsc,
|
||||
trx->cfg.rach_delay, trx->cfg.egprs, trx->cfg.ext_rach)) {
|
||||
transceiver = new Transceiver(&trx->cfg, GSM::Time(3,0), radio);
|
||||
if (!transceiver->init()) {
|
||||
LOG(ALERT) << "Failed to initialize transceiver";
|
||||
return -1;
|
||||
}
|
||||
|
Reference in New Issue
Block a user