diff options
Diffstat (limited to 'Transceiver52M')
24 files changed, 3429 insertions, 568 deletions
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am index 7dad159..a205d6b 100644 --- a/Transceiver52M/Makefile.am +++ b/Transceiver52M/Makefile.am @@ -30,6 +30,8 @@ AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_C noinst_LTLIBRARIES = libtransceiver_common.la COMMON_SOURCES = \ + fbsb.cpp \ + l1if.cpp \ radioInterface.cpp \ radioVector.cpp \ radioClock.cpp \ @@ -37,10 +39,12 @@ COMMON_SOURCES = \ sigProcLib.cpp \ signalVector.cpp \ Transceiver.cpp \ + Transceiver2.cpp \ ChannelizerBase.cpp \ Channelizer.cpp \ Synthesis.cpp \ - proto_trxd.c + proto_trxd.c \ + sch.c libtransceiver_common_la_SOURCES = \ $(COMMON_SOURCES) \ @@ -57,6 +61,7 @@ noinst_HEADERS = \ sigProcLib.h \ signalVector.h \ Transceiver.h \ + Transceiver2.h \ Resampler.h \ ChannelizerBase.h \ Channelizer.h \ @@ -68,6 +73,7 @@ COMMON_LDADD = \ $(ARCH_LA) \ $(GSM_LA) \ $(COMMON_LA) \ + $(TRXCON_LA) \ $(FFTWF_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOCTRL_LIBS) \ @@ -81,7 +87,8 @@ osmo_trx_uhd_SOURCES = osmo-trx.cpp osmo_trx_uhd_LDADD = \ $(builddir)/device/uhd/libdevice.la \ $(COMMON_LDADD) \ - $(UHD_LIBS) + $(UHD_LIBS) \ + $(TRXCON_LA) osmo_trx_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) endif diff --git a/Transceiver52M/Transceiver2.cpp b/Transceiver52M/Transceiver2.cpp new file mode 100644 index 0000000..595508e --- /dev/null +++ b/Transceiver52M/Transceiver2.cpp @@ -0,0 +1,1198 @@ +/* +* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "osmocom/core/bits.h" +#include <stdio.h> +#include <Logger.h> +#include "Transceiver2.h" + +extern "C" { +#include "sch.h" +} + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "l1if.h" + +using namespace GSM; + +#define USB_LATENCY_INTRVL 10,0 + +#if USE_UHD +# define USB_LATENCY_MIN 6,7 +#else +# define USB_LATENCY_MIN 1,1 +#endif + +/* Clock indication interval in frames */ +#define CLK_IND_INTERVAL 100 + +/* Number of running values use in noise average */ +#define NOISE_CNT 20 +#define FREQ_CNT 20 + +TransceiverState::TransceiverState() + : mRetrans(false), mFreqOffsets(FREQ_CNT), mode(Transceiver2::TRX_MODE_OFF) +{ + for (int i = 0; i < 8; i++) { + chanType[i] = Transceiver2::NONE; + fillerModulus[i] = 26; + prevFrame[i] = NULL; + + for (int n = 0; n < 102; n++) + fillerTable[n][i] = NULL; + } +} + +TransceiverState::~TransceiverState() +{ + for (int i = 0; i < 8; i++) { + for (int n = 0; n < 102; n++) + delete fillerTable[n][i]; + } +} + +void TransceiverState::init(size_t slot, signalVector *burst, bool fill) +{ + signalVector *filler; + + for (int i = 0; i < 102; i++) { + if (fill) + filler = new signalVector(*burst); + else + filler = new signalVector(burst->size()); + + fillerTable[i][slot] = filler; + } +} + +Transceiver2::Transceiver2(int wBasePort, + const char *TRXAddress, + size_t wSPS, size_t wChans, + GSM::Time wTransmitLatency, + RadioInterface *wRadioInterface) + : rx_sps(4), tx_sps(4), mAddr(TRXAddress), + mTransmitLatency(wTransmitLatency), + mRadioInterface(wRadioInterface), mChans(wChans), + mOn(false), mTxFreq(0.0), mRxFreq(0.0), mPower(-10), mMaxExpectedDelay(0), + mBSIC(-1) +{ + GSM::Time startTime(random() % gHyperframe,0); + + mLowerLoopThread = new Thread(32768); + + mTransmitDeadlineClock = startTime; + mLastClockUpdateTime = startTime; + mLatencyUpdateTime = startTime; + mRadioInterface->getClock()->set(startTime); + + txFullScale = mRadioInterface->fullScaleInputValue(); + rxFullScale = mRadioInterface->fullScaleOutputValue(); + + for (int i = 0; i < 8; i++) + mRxSlotMask[i] = 0; +} + +Transceiver2::~Transceiver2() +{ + stop(); + + sigProcLibDestroy(); + + for (size_t i = 0; i < mChans; i++) { + mTxPriorityQueues[i].clear(); + } +} + +bool Transceiver2::init(bool filler) +{ + + if (!mChans) { + LOG(ALERT) << "No channels assigned"; + return false; + } + + if (!sigProcLibSetup()) { + LOG(ALERT) << "Failed to initialize signal processing library"; + return false; + } + + mControlServiceLoopThreads.resize(mChans); + mTxPriorityQueueServiceLoopThreads.resize(mChans); + mRxServiceLoopThreads.resize(mChans); + + mTxPriorityQueues.resize(mChans); + mReceiveFIFO.resize(mChans); + mStates.resize(mChans); + + /* Filler table retransmissions - support only on channel 0 */ + if (filler) + mStates[0].mRetrans = true; + + + for (size_t i = 0; i < mChans; i++) { + mControlServiceLoopThreads[i] = new Thread(32768); + mTxPriorityQueueServiceLoopThreads[i] = new Thread(32768); + mRxServiceLoopThreads[i] = new Thread(32768); + + for (size_t n = 0; n < 8; n++) { + // burst = modulateBurst(gDummyBurst, 8 + (n % 4 == 0), tx_sps); + // scaleVector(*burst, txFullScale); + // mStates[i].init(n, burst, filler && !i); + // delete burst; + } + } + + return true; +} + +void Transceiver2::addRadioVector(size_t chan, BitVector &bits, + int RSSI, GSM::Time &wTime) +{ + signalVector *burst; + radioVector *radio_burst; + + if (chan >= mTxPriorityQueues.size()) { + LOG(ALERT) << "Invalid channel " << chan; + return; + } + + if (wTime.TN() > 7) { + LOG(ALERT) << "Received burst with invalid slot " << wTime.TN(); + return; + } + + if (mStates[0].mode != TRX_MODE_BTS) + return; + + burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), tx_sps); + scaleVector(*burst, txFullScale * pow(10, -RSSI / 10)); + + radio_burst = new radioVector(wTime, burst); + + mTxPriorityQueues[chan].write(radio_burst); +} + +void Transceiver2::updateFillerTable(size_t chan, radioVector *burst) +{ + int TN, modFN; + TransceiverState *state = &mStates[chan]; + + TN = burst->getTime().TN(); + modFN = burst->getTime().FN() % state->fillerModulus[TN]; + + delete state->fillerTable[modFN][TN]; + state->fillerTable[modFN][TN] = burst->getVector(); + burst->setVector(NULL); +} + +void Transceiver2::pushRadioVector(GSM::Time &nowTime) +{ + int TN, modFN; + radioVector *burst; + TransceiverState *state; + std::vector<signalVector *> bursts(mChans); + std::vector<bool> zeros(mChans, false); + std::vector<bool> filler(mChans, true); + + for (size_t i = 0; i < mChans; i ++) { + state = &mStates[i]; + + while ((burst = mTxPriorityQueues[i].getStaleBurst(nowTime))) { + LOG(NOTICE) << "dumping STALE burst in TRX->USRP interface"; + if (state->mRetrans) + updateFillerTable(i, burst); + delete burst; + } + + TN = nowTime.TN(); + modFN = nowTime.FN() % state->fillerModulus[TN]; + + bursts[i] = state->fillerTable[modFN][TN]; + if (state->mode == TRX_MODE_BTS) + zeros[i] = state->chanType[TN] == NONE; + + if ((burst = mTxPriorityQueues[i].getCurrentBurst(nowTime))) { + bursts[i] = burst->getVector(); + + if (state->mRetrans) { + updateFillerTable(i, burst); + } else { + burst->setVector(NULL); + filler[i] = false; + } + + delete burst; + } + } + + mRadioInterface->driveTransmitRadio(bursts, zeros); + + for (size_t i = 0; i < mChans; i++) { + if (!filler[i]) + delete bursts[i]; + } +} + +void Transceiver2::setModulus(size_t timeslot, size_t chan) +{ + TransceiverState *state = &mStates[chan]; + + switch (state->chanType[timeslot]) { + case NONE: + case I: + case II: + case III: + case FILL: + state->fillerModulus[timeslot] = 26; + break; + case IV: + case VI: + case V: + state->fillerModulus[timeslot] = 51; + break; + //case V: + case VII: + state->fillerModulus[timeslot] = 102; + break; + case XIII: + state->fillerModulus[timeslot] = 52; + break; + default: + break; + } +} + + +CorrType Transceiver2::expectedCorrType(GSM::Time currTime, + size_t chan) +{ + TransceiverState *state = &mStates[chan]; + unsigned burstTN = currTime.TN(); + unsigned burstFN = currTime.FN(); + + if (state->mode == TRX_MODE_MS_TRACK) { + /* 102 modulus case currently unhandled */ + if (state->fillerModulus[burstTN] > 52) + return OFF; + + int modFN = burstFN % state->fillerModulus[burstTN]; + unsigned long long reg = (unsigned long long) 1 << modFN; + if (reg & mRxSlotMask[burstTN]) + return TSC; + else + return OFF; + } + + switch (state->chanType[burstTN]) { + case NONE: + return OFF; + break; + case FILL: + return IDLE; + break; + case I: + return TSC; + /*if (burstFN % 26 == 25) + return IDLE; + else + return TSC;*/ + break; + case II: + return TSC; + break; + case III: + return TSC; + break; + case IV: + case VI: + return RACH; + break; + case V: { + int mod51 = burstFN % 51; + if ((mod51 <= 36) && (mod51 >= 14)) + return RACH; + else if ((mod51 == 4) || (mod51 == 5)) + return RACH; + else if ((mod51 == 45) || (mod51 == 46)) + return RACH; + else + return TSC; + break; + } + case VII: + if ((burstFN % 51 <= 14) && (burstFN % 51 >= 12)) + return IDLE; + else + return TSC; + break; + case XIII: { + int mod52 = burstFN % 52; + if ((mod52 == 12) || (mod52 == 38)) + return RACH; + else if ((mod52 == 25) || (mod52 == 51)) + return IDLE; + else + return TSC; + break; + } + case LOOPBACK: + if ((burstFN % 51 <= 50) && (burstFN % 51 >=48)) + return IDLE; + else + return TSC; + break; + default: + return OFF; + break; + } +} + + +/* Detect SCH synchronization sequence within a burst */ +bool Transceiver2::detectSCH(TransceiverState *state, + signalVector &burst, + struct estim_burst_params *ebp) +{ + int shift; + sch_detect_type full; + float mag, threshold = 5.0; + + full = (state->mode == TRX_MODE_MS_TRACK) ? + sch_detect_type::SCH_DETECT_NARROW : sch_detect_type::SCH_DETECT_FULL; + + if (!detectSCHBurst(burst, threshold, rx_sps, full, ebp)) + return false; + + std::clog << "SCH : Timing offset " << ebp->toa << " symbols" << std::endl; + + mag = fabsf(ebp->toa); + if (mag < 1.0f) + return true; + + shift = (int) (mag / 2.0f); + if (!shift) + shift++; + + shift = ebp->toa > 0 ? shift : -shift; + std::clog << "SCH : shift -> " << shift << " symbols" << std::endl; + mRadioInterface->applyOffset(shift); + return false; +} + +#define SCH_BIT_SCALE 64 + +/* Decode SCH burst */ +bool Transceiver2::decodeSCH(SoftVector *burst, GSM::Time *time) +{ + int fn; + struct sch_info sch; + ubit_t info[GSM_SCH_INFO_LEN]; + sbit_t data[GSM_SCH_CODED_LEN]; + + if (burst->size() < 156) { + std::clog << "Invalid SCH burst length" << std::endl; + return false; + } + + float_to_sbit(&(*burst)[3], &data[0], SCH_BIT_SCALE, 39); + float_to_sbit(&(*burst)[106], &data[39], SCH_BIT_SCALE, 39); + + if (!gsm_sch_decode(info, data)) { + gsm_sch_parse(info, &sch); + + mBSIC = sch.bsic; + mTSC = mBSIC & 0x7; + +// std::clog << "SCH : Decoded values" << std::endl; +// std::clog << " BSIC: " << sch.bsic << std::endl; +// std::clog << " T1 : " << sch.t1 << std::endl; +// std::clog << " T2 : " << sch.t2 << std::endl; +// std::clog << " T3p : " << sch.t3p << std::endl; +// std::clog << " FN : " << gsm_sch_to_fn(&sch) << std::endl; + + fn = gsm_sch_to_fn(&sch); + if (fn < 0) { + std::clog << "SCH : Failed to convert FN " << std::endl; + return false; + } + + time->FN(fn); + time->TN(0); + } else { + std::clog << "Invalid SCH decode!!" << std::endl; + return false; + } + + return true; +} + +#define FCCH_OFFSET_LIMIT 5e3 +#define FCCH_ADJUST_LIMIT 20.0 + +/* Apply FCCH frequency correction */ +bool Transceiver2::correctFCCH(TransceiverState *state, signalVector *burst) +{ + double offset; + + if (!burst) + return false; + + offset = gsm_fcch_offset((float *) burst->begin(), burst->size()); + //std::cout << "XXXX FCCH: Frequency offset " << offset << " Hz" << std::endl; + if (offset > FCCH_OFFSET_LIMIT) + return false; + + state->mFreqOffsets.insert(offset); + + if (state->mFreqOffsets.full()) { + double avg = state->mFreqOffsets.avg(); + std::clog << "FCCH: Frequency offset " << avg << " Hz" << std::endl; + + if (fabs(avg) > FCCH_ADJUST_LIMIT) { + mRadioInterface->tuneRxOffset(-avg); + state->mFreqOffsets.reset(); + } + } + + return true; +} + +/* + * Pull bursts from the FIFO and handle according to the slot + * and burst correlation type. Equalzation is currently disabled. + */ +SoftVector *Transceiver2::pullRadioVector(GSM::Time &wTime, int &RSSI, + int &timingOffset, size_t chan) __attribute__((optnone)) +{ + struct estim_burst_params ebp; + int rc; + float pow, max = -1.0, avg = 1.0; + int max_i = -1; + signalVector *burst; + SoftVector *bits = NULL; + TransceiverState *state = &mStates[chan]; + + GSM::Time sch_time, burst_time, diff_time; + + /* Blocking FIFO read */ + radioVector *radio_burst = mReceiveFIFO[chan]->read(); + if (!radio_burst) + return NULL; + + /* Set time and determine correlation type */ + burst_time = radio_burst->getTime(); + +#if 0 + if (state->mode == TRX_MODE_MS_ACQUIRE) { + switch(fbsb_acq_buf.s.load()) { + case fbsb_par::fbsb_state::IDLE: + case fbsb_par::fbsb_state::INIT: + fbsb_acq_buf.s.store(fbsb_par::fbsb_state::ACQ); + case fbsb_par::fbsb_state::ACQ: + fbsb_acq_buf.take(radio_burst->getVector()->begin(), radio_burst->getVector()->size(), burst_time); + + if(!fbsb_acq_buf.done()) { + delete radio_burst; + return nullptr; + } + fbsb_acq_buf.s.store(fbsb_par::fbsb_state::ACQ_COMPL); +// break; + case fbsb_par::fbsb_state::ACQ_COMPL: + + + { + complex famp = 0; + int found_index; + auto foundat = fbsb_acq_buf.fcch(&famp, &found_index, false); + std::cerr << "@ " << found_index << std::endl; + foundat = found_index; + + auto framelen = (3 + 142 + 3 + 8.25); // 1sps!; + int searchbegin = 8*framelen-20; + int searchend = 10*framelen+20; + + if(famp.abs() < 2000 || foundat+searchend > fbsb_acq_buf.sz()) { + mReceiveFIFO[chan]->clear(); + delete radio_burst; + fbsb_acq_buf.reset(); + fbsb_acq_buf.s.store(fbsb_par::fbsb_state::ACQ); + return nullptr; + } + correctFCCH_raw(state, &fbsb_acq_buf.fbsb_buf[foundat], 98); + + burst = new signalVector(searchend-searchbegin, GSM::gRACHSynchSequence.size()); +// burst = new signalVector(fbsb_acq_buf.fbsb_buf, foundat+searchbegin, searchend-searchbegin); + memcpy(burst->begin(), &fbsb_acq_buf.fbsb_buf[foundat+searchbegin], (searchend-searchbegin) * sizeof(complex)); + success = detectSCH(state, *burst, amp, toa, 0); // will "fail" if sample adjustment is required + std::cerr << "###detected sch: " << success << "at toa " << toa << std::endl; + mReceiveFIFO[chan]->clear(); + delete radio_burst; + + if(toa > 0) { + int how_many_ts = (found_index+searchbegin)/framelen; +// int how_many_fn = how_many_ts/8; + auto t = fbsb_acq_buf.rcvClock[how_many_ts]; + + diff_time = GSM::Time(sch_time.FN() - t.FN(),-t.TN()); + mRadioInterface->adjustClock(diff_time); + mTransmitDeadlineClock = RadioClock::adjust( + mTransmitDeadlineClock, + diff_time); + } + if(!success) { + fbsb_acq_buf.reset(); + fbsb_acq_buf.s.store(fbsb_par::fbsb_state::ACQ); + } + else + fbsb_acq_buf.s.store(fbsb_par::fbsb_state::DONE); + return nullptr; + } + case fbsb_par::fbsb_state::DONE: + break; + default: + // fbsb_acq_buf.s = fbsb_par::fbsb_state::WAIT; + return nullptr; + break; + /* no-op */ + } + + } +#endif + CorrType type = expectedCorrType(burst_time, chan); + + switch (state->mode) { + case TRX_MODE_MS_ACQUIRE: + type = SCH; + break; + case TRX_MODE_MS_TRACK: + if (gsm_sch_check_fn(burst_time.FN()) && burst_time.TN() == 0) + type = SCH; + else if(burst_time.TN() == 0 && !gsm_fcch_check_fn(burst_time.FN())) // all ts0, but not fcch or sch.. + type = TSC; + else if (type == OFF) + goto release; + break; + case TRX_MODE_BTS: + if ((type == TSC) || (type == RACH)) + break; + case TRX_MODE_OFF: + default: + goto release; + } + + /* 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 * rx_sps); + if (pow > max) { + max = pow; + max_i = i; + } + avg += pow; + } + + if (max_i < 0) { + LOG(ALERT) << "Received empty burst"; + goto release; + } + + /* Average noise on diversity paths and update global levels */ + burst = radio_burst->getVector(max_i); + avg = sqrt(avg / radio_burst->chans()); + + + /* Detect normal or RACH bursts */ + if (type == SCH) { + rc = detectSCH(state, *burst, &ebp); + rc = rc > 0 ? rc : -1; + } else { + rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, rx_sps, type, mMaxExpectedDelay, &ebp); + if(rc > 0) { + type = (CorrType)rc; + } + } + + if (rc < 0) + goto release; + + + /* Ignore noise threshold on MS mode for now */ + //if ((type == SCH) || (avg - state->mNoiseLev > 0.0)) + bits = demodAnyBurst(*burst, type, rx_sps, &ebp); + + /* MS: Decode SCH and adjust GSM clock */ + if ((type != TSC) && + ((state->mode == TRX_MODE_MS_ACQUIRE) || + (state->mode == TRX_MODE_MS_TRACK))) { + correctFCCH(state, state->prevFrame[burst_time.TN()]->getVector()); + + if (decodeSCH(bits, &sch_time)) { + if (state->mode == TRX_MODE_MS_ACQUIRE) { + diff_time = GSM::Time(sch_time.FN() - burst_time.FN(), + -burst_time.TN()); + mRadioInterface->adjustClock(diff_time); + mTransmitDeadlineClock = RadioClock::adjust( + mTransmitDeadlineClock, + diff_time); + state->mode = TRX_MODE_MS_TRACK; + + burst_time = sch_time; //sch bits passed up might have a different time, so upper layers complain + std::clog << "SCH : Locking GSM clock " << std::endl; + } else { + //std::clog << "SCH : Read SCH at FN " << burst_time.FN() << " FN51 " << burst_time.FN() % 51 << std::endl; + wTime = burst_time; + RSSI = (int) floor(20.0 * log10(rxFullScale / avg)); + timingOffset = (int) round(ebp.toa * 256.0 / rx_sps); + return bits; + } + } + else + std::clog << "SCH : FAIL!!!!! SCH at FN " << burst_time.FN() << std::endl; + + goto release; + } + + wTime = burst_time; + RSSI = (int) floor(20.0 * log10(rxFullScale / avg)); + timingOffset = (int) round(ebp.toa * 256.0 / rx_sps); + + delete state->prevFrame[burst_time.TN()]; + state->prevFrame[burst_time.TN()] = radio_burst; + + return bits; + +release: + delete state->prevFrame[burst_time.TN()]; + state->prevFrame[burst_time.TN()] = radio_burst; + delete bits; + return NULL; +} + +void Transceiver2::start() +{ + TransceiverChannel *chan; + + for (size_t i = 0; i < mControlServiceLoopThreads.size(); i++) { + chan = new TransceiverChannel(this, i); + mControlServiceLoopThreads[i]->start((void * (*)(void*)) + ControlServiceLoopAdapter, (void*) chan); + } +} + +void Transceiver2::stop() +{ + + if (!mOn) + return; + + LOG(NOTICE) << "Stopping the transceiver"; + mLowerLoopThread->cancel(); + mLowerLoopThread->join(); + delete mLowerLoopThread; + + + for (size_t i = 0; i < mChans; i++) { + mRxServiceLoopThreads[i]->cancel(); + mTxPriorityQueueServiceLoopThreads[i]->cancel(); + } + + LOG(INFO) << "Stopping the device"; + mRadioInterface->stop(); + + for (size_t i = 0; i < mChans; i++) { + mRxServiceLoopThreads[i]->join(); + mTxPriorityQueueServiceLoopThreads[i]->join(); + delete mRxServiceLoopThreads[i]; + delete mTxPriorityQueueServiceLoopThreads[i]; + + mTxPriorityQueues[i].clear(); + } + + mOn = false; + LOG(NOTICE) << "Transceiver stopped"; +} + + +void Transceiver2::reset() +{ + for (size_t i = 0; i < mTxPriorityQueues.size(); i++) + mTxPriorityQueues[i].clear(); +} + + +void Transceiver2::driveControl(size_t chan) +{ + //char response[MAX_PACKET_LENGTH]; + + // check control socket +// char buffer[MAX_PACKET_LENGTH]; +// int msgLen = -1; +// buffer[0] = '\0'; + + +//// msgLen = mCtrlSockets[chan]->read(buffer); + +// if (msgLen < 1) { +// return; +// } + + auto m = pop_c(); + if(!m) + return; + +auto response = (TRX_C*)malloc(sizeof(TRX_C)); +response->cmd[0] = '\0'; +commandhandler(m->cmd, response->cmd, chan); +free(m); +std::clog << "response is " << response->cmd << std::endl; +push_c(response); + //mCtrlSockets[chan]->write(response, strlen(response) + 1); +} + +void Transceiver2::commandhandler(char* buffer, char* response, int chan) +{ + int MAX_PACKET_LENGTH = TRXC_BUF_SIZE; + + char cmdcheck[4]; + char command[MAX_PACKET_LENGTH]; + + + sscanf(buffer,"%3s %s",cmdcheck,command); + + if (!chan) + writeClockInterface(); + + if (strcmp(cmdcheck,"CMD")!=0) { + LOG(WARNING) << "bogus message on control interface"; + return; + } + std::clog << "command is " << buffer << std::endl << std::flush; + + if (strcmp(command,"MEASURE")==0) { + msleep(100); + int freq; + sscanf(buffer,"%3s %s %d",cmdcheck,command,&freq); + sprintf(response,"RSP MEASURE 0 %d -80",freq); + } + else if (strcmp(command,"ECHO")==0) { + msleep(100); + sprintf(response,"RSP ECHO 0"); + } + else if (strcmp(command,"POWEROFF")==0) { + // turn off transmitter/demod + sprintf(response,"RSP POWEROFF 0"); + } + else if (strcmp(command,"POWERON")==0) { + // turn on transmitter/demod + if (!mTxFreq || !mRxFreq) + sprintf(response,"RSP POWERON 1"); + else { + sprintf(response,"RSP POWERON 0"); + if (!chan && !mOn) { + // Prepare for thread start + mPower = -20; + mRadioInterface->start(); + + // Start radio interface threads. + mLowerLoopThread->start((void * (*)(void*)) + LowerLoopAdapter,(void*) this); + + for (size_t i = 0; i < mChans; i++) { + TransceiverChannel *chan = new TransceiverChannel(this, i); + mRxServiceLoopThreads[i]->start((void * (*)(void*)) + RxUpperLoopAdapter, (void*) chan); + + // chan = new TransceiverChannel(this, i); + // mTxPriorityQueueServiceLoopThreads[i]->start((void * (*)(void*)) + // TxUpperLoopAdapter, (void*) chan); + } + + writeClockInterface(); + mOn = true; + } + } + } + else if (strcmp(command,"SETMAXDLY")==0) { + //set expected maximum time-of-arrival + int maxDelay; + sscanf(buffer,"%3s %s %d",cmdcheck,command,&maxDelay); + mMaxExpectedDelay = maxDelay; // 1 GSM symbol is approx. 1 km + sprintf(response,"RSP SETMAXDLY 0 %d",maxDelay); + } + else if (strcmp(command,"SETRXGAIN")==0) { + //set expected maximum time-of-arrival + int newGain; + sscanf(buffer,"%3s %s %d",cmdcheck,command,&newGain); + newGain = mRadioInterface->setRxGain(newGain, chan); + sprintf(response,"RSP SETRXGAIN 0 %d",newGain); + } + else if (strcmp(command,"NOISELEV")==0) { + if (mOn) { + float lev = 0;//mStates[chan].mNoiseLev; + sprintf(response,"RSP NOISELEV 0 %d", + (int) round(20.0 * log10(rxFullScale / lev))); + } + else { + sprintf(response,"RSP NOISELEV 1 0"); + } + } + else if (!strcmp(command, "SETPOWER")) { + // set output power in dB + int dbPwr; + sscanf(buffer, "%3s %s %d", cmdcheck, command, &dbPwr); + if (!mOn) + sprintf(response, "RSP SETPOWER 1 %d", dbPwr); + else { + mPower = dbPwr; + mRadioInterface->setPowerAttenuation(mPower, chan); + sprintf(response, "RSP SETPOWER 0 %d", dbPwr); + } + } + else if (!strcmp(command,"ADJPOWER")) { + // adjust power in dB steps + int dbStep; + sscanf(buffer, "%3s %s %d", cmdcheck, command, &dbStep); + if (!mOn) + sprintf(response, "RSP ADJPOWER 1 %d", mPower); + else { + mPower += dbStep; + mRadioInterface->setPowerAttenuation(mPower, chan); + sprintf(response, "RSP ADJPOWER 0 %d", mPower); + } + } + else if (strcmp(command,"RXTUNE")==0) { + // tune receiver + int freqKhz; + sscanf(buffer,"%3s %s %d",cmdcheck,command,&freqKhz); + mRxFreq = freqKhz * 1e3; + if (!mRadioInterface->tuneRx(mRxFreq, chan)) { + LOG(ALERT) << "RX failed to tune"; + sprintf(response,"RSP RXTUNE 1 %d",freqKhz); + } + else + sprintf(response,"RSP RXTUNE 0 %d",freqKhz); + } + else if (strcmp(command,"TXTUNE")==0) { + // tune txmtr + int freqKhz; + sscanf(buffer,"%3s %s %d",cmdcheck,command,&freqKhz); + mTxFreq = freqKhz * 1e3; + if (!mRadioInterface->tuneTx(mTxFreq, chan)) { + LOG(ALERT) << "TX failed to tune"; + sprintf(response,"RSP TXTUNE 1 %d",freqKhz); + } + else + sprintf(response,"RSP TXTUNE 0 %d",freqKhz); + } + else if (!strcmp(command,"SETTSC")) { + // set TSC + unsigned TSC; + sscanf(buffer, "%3s %s %d", cmdcheck, command, &TSC); + if (mOn) + sprintf(response, "RSP SETTSC 1 %d", TSC); + else if (chan && (TSC != mTSC)) + sprintf(response, "RSP SETTSC 1 %d", TSC); + else { + mTSC = TSC; + //generateMidamble(rx_sps, TSC); + sprintf(response,"RSP SETTSC 0 %d", TSC); + } + } + else if (!strcmp(command,"GETBSIC")) { + if (mBSIC < 0) + sprintf(response, "RSP GETBSIC 1"); + else + sprintf(response, "RSP GETBSIC 0 %d", mBSIC); + } + else if (strcmp(command,"SETSLOT")==0) { + // set TSC + int corrCode; + int timeslot; + sscanf(buffer,"%3s %s %d %d",cmdcheck,command,×lot,&corrCode); + if ((timeslot < 0) || (timeslot > 7)) { + LOG(WARNING) << "bogus message on control interface"; + sprintf(response,"RSP SETSLOT 1 %d %d",timeslot,corrCode); + return; + } + mStates[chan].chanType[timeslot] = (ChannelCombination) corrCode; + setModulus(timeslot, chan); + sprintf(response,"RSP SETSLOT 0 %d %d",timeslot,corrCode); + } + else if (!strcmp(command,"SETRXMASK")) { + int slot; + unsigned long long mask; + sscanf(buffer,"%3s %s %d 0x%llx", cmdcheck, command, &slot, &mask); + if ((slot < 0) || (slot > 7)) { + sprintf(response, "RSP SETRXMASK 1"); + } else { + mRxSlotMask[slot] = mask; + sprintf(response, "RSP SETRXMASK 0 %d 0x%llx", slot, mask); + } + } + else if (!strcmp(command, "SYNC")) { + msleep(10); + mStates[0].mode = TRX_MODE_MS_ACQUIRE; + sprintf(response,"RSP SYNC 0"); + mMaxExpectedDelay = 10; + mRadioInterface->setRxGain(30, 0); + msleep(10); + } + else { + LOG(WARNING) << "bogus command " << command << " on control interface."; + } + + //mCtrlSockets[chan]->write(response, strlen(response) + 1); +} + +bool Transceiver2::driveTxPriorityQueue(size_t chan) +{ + auto burst = pop_d(); + if(!burst) + return true; + + auto currTime = GSM::Time(burst->fn,burst->ts); + int RSSI = (int) burst->txlev; + + static BitVector newBurst(gSlotLen); + BitVector::iterator itr = newBurst.begin(); + auto *bufferItr = burst->symbols; + while (itr < newBurst.end()) + *itr++ = *bufferItr++; + + addRadioVector(chan, newBurst, RSSI, currTime); + free(burst); + return true; + + + +// char buffer[gSlotLen+50]; + +// // check data socket +// size_t msgLen = mDataSockets[chan]->read(buffer); + +// if (msgLen!=gSlotLen+1+4+1) { +// LOG(ERR) << "badly formatted packet on GSM->TRX interface"; +// return false; +// } + +// int timeSlot = (int) buffer[0]; +// uint64_t frameNum = 0; +// for (int i = 0; i < 4; i++) +// frameNum = (frameNum << 8) | (0x0ff & buffer[i+1]); + +// LOG(DEBUG) << "rcvd. burst at: " << GSM::Time(frameNum,timeSlot); + +// int RSSI = (int) buffer[5]; +// static BitVector newBurst(gSlotLen); +// BitVector::iterator itr = newBurst.begin(); +// char *bufferItr = buffer+6; +// while (itr < newBurst.end()) +// *itr++ = *bufferItr++; + +// GSM::Time currTime = GSM::Time(frameNum,timeSlot); + +// addRadioVector(chan, newBurst, RSSI, currTime); + + return true; + + +} + +void Transceiver2::driveReceiveRadio() +{ + if (!mRadioInterface->driveReceiveRadio()) + usleep(100000); +} + +void Transceiver2::driveReceiveFIFO(size_t chan) +{ + SoftVector *rxBurst = NULL; + int RSSI; + int TOA; // in 1/256 of a symbol + GSM::Time burstTime; + + rxBurst = pullRadioVector(burstTime, RSSI, TOA, chan); + + if (rxBurst) { + auto response = (trxd_from_trx*)malloc(sizeof(trxd_from_trx)); +#if 0 + if( !gsm_fcch_check_fn(burstTime.FN()) && !gsm_sch_check_fn(burstTime.FN())) + std::cerr << "burst parameters: " + << " time: " << burstTime + << " RSSI: " << RSSI + << " TOA: " << TOA + << " bits: " << *rxBurst << std::endl; +#endif + response->ts = burstTime.TN(); + response->fn = burstTime.FN(); + response->rssi = RSSI; + response->toa = TOA; + + SoftVector::const_iterator burstItr = rxBurst->begin(); + if(gsm_sch_check_fn(burstTime.FN())) { + for (unsigned int i = 0; i < gSlotLen; i++) + ((int8_t*)response->symbols)[i] = round(((*burstItr++)-0.5) * 64.0); + } else { + // invert and fix to +-127 sbits + for (int i = 0; i < 148; i++) + ((int8_t*)response->symbols)[i] = *burstItr++ > 0.0f ? -127 : 127; + } + + delete rxBurst; + push_d(response); + + } +} + +void Transceiver2::driveTxFIFO() +{ + + /** + Features a carefully controlled latency mechanism, to + assure that transmit packets arrive at the radio/USRP + before they need to be transmitted. + + Deadline clock indicates the burst that needs to be + pushed into the FIFO right NOW. If transmit queue does + not have a burst, stick in filler data. + */ + + + RadioClock *radioClock = (mRadioInterface->getClock()); + + if (mOn) { + //radioClock->wait(); // wait until clock updates + LOG(DEBUG) << "radio clock " << radioClock->get(); + while (radioClock->get() + mTransmitLatency > mTransmitDeadlineClock) { + // if underrun, then we're not providing bursts to radio/USRP fast + // enough. Need to increase latency by one GSM frame. + if (mRadioInterface->getWindowType() == RadioDevice::TX_WINDOW_USRP1) { + if (mRadioInterface->isUnderrun()) { + // only update latency at the defined frame interval + if (radioClock->get() > mLatencyUpdateTime + GSM::Time(USB_LATENCY_INTRVL)) { + mTransmitLatency = mTransmitLatency + GSM::Time(1,0); + LOG(INFO) << "new latency: " << mTransmitLatency; + mLatencyUpdateTime = radioClock->get(); + } + } + else { + // if underrun hasn't occurred in the last sec (216 frames) drop + // transmit latency by a timeslot + if (mTransmitLatency > GSM::Time(USB_LATENCY_MIN)) { + if (radioClock->get() > mLatencyUpdateTime + GSM::Time(216,0)) { + mTransmitLatency.decTN(); + //LOG(INFO) << "reduced latency: " << mTransmitLatency; + mLatencyUpdateTime = radioClock->get(); + } + } + } + } + // time to push burst to transmit FIFO + pushRadioVector(mTransmitDeadlineClock); + + mTransmitDeadlineClock.incTN(); + + if (!mTransmitDeadlineClock.TN() && + !(mTransmitDeadlineClock.FN() % CLK_IND_INTERVAL)) { + writeClockInterface(); + } + } + } + + radioClock->wait(); +} + + + +void Transceiver2::writeClockInterface() +{ + mLastClockUpdateTime = mTransmitDeadlineClock; +} + +void *RxUpperLoopAdapter(TransceiverChannel *chan) +{ + Transceiver2 *trx = chan->trx; + size_t num = chan->num; + + delete chan; + + // trx->setPriority(0.42); + + while (1) { + trx->driveReceiveFIFO(num); + pthread_testcancel(); + } + return NULL; +} + +void *LowerLoopAdapter(Transceiver2 *transceiver) +{ + // transceiver->setPriority(0.45); + + while (1) { + transceiver->driveReceiveRadio(); + //transceiver->driveTxFIFO(); + pthread_testcancel(); + } + return NULL; +} + +void *ControlServiceLoopAdapter(TransceiverChannel *chan) +{ + Transceiver2 *trx = chan->trx; + size_t num = chan->num; + + delete chan; + + while (1) { + trx->driveControl(num); + pthread_testcancel(); + } + return NULL; +} + +void *TxUpperLoopAdapter(TransceiverChannel *chan) +{ + Transceiver2 *trx = chan->trx; + size_t num = chan->num; + + delete chan; + + // trx->setPriority(0.40); + + while (1) { + bool stale = false; + // Flush the UDP packets until a successful transfer. + while (!trx->driveTxPriorityQueue(num)) { + stale = true; + } + if (!num && stale) { + // If a packet was stale, remind the GSM stack of the clock. + trx->writeClockInterface(); + } + pthread_testcancel(); + } + return NULL; +} diff --git a/Transceiver52M/Transceiver2.h b/Transceiver52M/Transceiver2.h new file mode 100644 index 0000000..e5579f4 --- /dev/null +++ b/Transceiver52M/Transceiver2.h @@ -0,0 +1,258 @@ +/* +* Copyright 2008 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "radioInterface.h" +#include "Interthread.h" +#include "GSMCommon.h" + +#include <sys/types.h> +#include <sys/socket.h> + +extern "C" { +#include <osmocom/core/signal.h> +#include <osmocom/core/select.h> +#include "config_defs.h" +} + +class Transceiver2; + +/** Channel descriptor for transceiver object and channel number pair */ +struct TransceiverChannel { + TransceiverChannel(Transceiver2 *trx, int num) + { + this->trx = trx; + this->num = num; + } + + ~TransceiverChannel() + { + } + + Transceiver2 *trx; + size_t num; +}; + +/** Internal transceiver state variables */ +struct TransceiverState { + TransceiverState(); + ~TransceiverState(); + + /* Initialize a multiframe slot in the filler table */ + void init(size_t slot, signalVector *burst, bool fill); + + int chanType[8]; + + /* The filler table */ + signalVector *fillerTable[102][8]; + int fillerModulus[8]; + bool mRetrans; + + /* Received noise energy levels */ + noiseVector mFreqOffsets; + + /* Store pointers to previous frame */ + radioVector *prevFrame[8]; + + /* Transceiver mode */ + int mode; +}; + +/** The Transceiver class, responsible for physical layer of basestation */ +class Transceiver2 { +private: + + int rx_sps, tx_sps; + std::string mAddr; + GSM::Time mTransmitLatency; ///< latency between basestation clock and transmit deadline clock + GSM::Time mLatencyUpdateTime; ///< last time latency was updated + + std::vector<VectorQueue> mTxPriorityQueues; ///< priority queue of transmit bursts received from GSM core + std::vector<VectorFIFO *> mReceiveFIFO; ///< radioInterface FIFO of receive bursts + + std::vector<Thread *> mRxServiceLoopThreads; ///< thread to pull bursts into receive FIFO + Thread *mLowerLoopThread; ///< thread to pull bursts into receive FIFO + std::vector<Thread *> mControlServiceLoopThreads; ///< thread to process control messages from GSM core + std::vector<Thread *> mTxPriorityQueueServiceLoopThreads; ///< thread to process transmit bursts from GSM core + + GSM::Time mTransmitDeadlineClock; ///< deadline for pushing bursts into transmit FIFO + GSM::Time mLastClockUpdateTime; ///< last time clock update was sent up to core + + RadioInterface *mRadioInterface; ///< associated radioInterface object + double txFullScale; ///< full scale input to radio + double rxFullScale; ///< full scale output to radio + + /** modulate and add a burst to the transmit queue */ + void addRadioVector(size_t chan, BitVector &bits, + int RSSI, GSM::Time &wTime); + + /** Update filler table */ + void updateFillerTable(size_t chan, radioVector *burst); + + /** Push modulated burst into transmit FIFO corresponding to a particular timestamp */ + void pushRadioVector(GSM::Time &nowTime); + + /** Pull and demodulate a burst from the receive FIFO */ + SoftVector *pullRadioVector(GSM::Time &wTime, int &RSSI, + int &timingOffset, size_t chan = 0); + + /** Set modulus for specific timeslot */ + void setModulus(size_t timeslot, size_t chan); + + /** return the expected burst type for the specified timestamp */ + CorrType expectedCorrType(GSM::Time currTime, size_t chan); + + /** send messages over the clock socket */ + void writeClockInterface(void); + + + bool detectSCH(TransceiverState *state, + signalVector &burst, + struct estim_burst_params *ebp); + + bool decodeSCH(SoftVector *burst, GSM::Time *time); + bool correctFCCH(TransceiverState *state, signalVector *burst); + + size_t mChans; + + bool mOn; ///< flag to indicate that transceiver is powered on + double mTxFreq; ///< the transmit frequency + double mRxFreq; ///< the receive frequency + int mPower; ///< the transmit power in dB + unsigned mTSC; ///< the midamble sequence code + unsigned mMaxExpectedDelay; ///< maximum TOA offset in GSM symbols + unsigned long long mRxSlotMask[8]; ///< MS - enabled multiframe slot mask + int mBSIC; ///< MS - detected BSIC + + std::vector<TransceiverState> mStates; + +public: + + /** Transceiver constructor + @param wBasePort base port number of UDP sockets + @param TRXAddress IP address of the TRX manager, as a string + @param wSPS number of samples per GSM symbol + @param wTransmitLatency initial setting of transmit latency + @param radioInterface associated radioInterface object + */ + Transceiver2(int wBasePort, + const char *TRXAddress, + size_t wSPS, size_t chans, + GSM::Time wTransmitLatency, + RadioInterface *wRadioInterface); + + /** Destructor */ + ~Transceiver2(); + + /** start the Transceiver */ + void start(); + bool init(bool filler); + + /** attach the radioInterface receive FIFO */ + bool receiveFIFO(VectorFIFO *wFIFO, size_t chan) + { + if (chan >= mReceiveFIFO.size()) + return false; + + mReceiveFIFO[chan] = wFIFO; + return true; + } + + /** accessor for number of channels */ + size_t numChans() const { return mChans; }; + + /** Codes for channel combinations */ + typedef enum { + FILL, ///< Channel is transmitted, but unused + I, ///< TCH/FS + II, ///< TCH/HS, idle every other slot + III, ///< TCH/HS + IV, ///< FCCH+SCH+CCCH+BCCH, uplink RACH + V, ///< FCCH+SCH+CCCH+BCCH+SDCCH/4+SACCH/4, uplink RACH+SDCCH/4 + VI, ///< CCCH+BCCH, uplink RACH + VII, ///< SDCCH/8 + SACCH/8 + VIII, ///< TCH/F + FACCH/F + SACCH/M + IX, ///< TCH/F + SACCH/M + X, ///< TCH/FD + SACCH/MD + XI, ///< PBCCH+PCCCH+PDTCH+PACCH+PTCCH + XII, ///< PCCCH+PDTCH+PACCH+PTCCH + XIII, ///< PDTCH+PACCH+PTCCH + NONE, ///< Channel is inactive, default + LOOPBACK ///< similar go VII, used in loopback testing + } ChannelCombination; + + enum { + TRX_MODE_OFF, + TRX_MODE_BTS, + TRX_MODE_MS_ACQUIRE, + TRX_MODE_MS_TRACK, + }; + + void commandhandler(char *buffer, char *response, int chan); + void stop(); +protected: + /** drive lower receive I/O and burst generation */ + void driveReceiveRadio(); + + /** drive demodulation of GSM bursts */ + void driveReceiveFIFO(size_t chan); + + /** drive transmission of GSM bursts */ + void driveTxFIFO(); + + /** drive handling of control messages from GSM core */ + void driveControl(size_t chan); + + /** + drive modulation and sorting of GSM bursts from GSM core + @return true if a burst was transferred successfully + */ + bool driveTxPriorityQueue(size_t chan); + + friend void *RxUpperLoopAdapter(TransceiverChannel *); + + friend void *TxUpperLoopAdapter(TransceiverChannel *); + + friend void *LowerLoopAdapter(Transceiver2 *); + + friend void *ControlServiceLoopAdapter(TransceiverChannel *); + + + void reset(); + + /** set priority on current thread */ + //void setPriority(float prio = 0.5) { mRadioInterface->setPriority(prio); } + +}; + +void *RxUpperLoopAdapter(TransceiverChannel *); + +/** Main drive threads */ +void *LowerLoopAdapter(Transceiver2 *); + +/** control message handler thread loop */ +void *ControlServiceLoopAdapter(TransceiverChannel *); + +/** transmit queueing thread loop */ +void *TxUpperLoopAdapter(TransceiverChannel *); + diff --git a/Transceiver52M/device/common/radioDevice.h b/Transceiver52M/device/common/radioDevice.h index 3f5da1f..1e19bdc 100644 --- a/Transceiver52M/device/common/radioDevice.h +++ b/Transceiver52M/device/common/radioDevice.h @@ -101,6 +101,9 @@ class RadioDevice { /** Set the receiver frequency */ virtual bool setRxFreq(double wFreq, size_t chan = 0) = 0; + /** Adjust the receiver offset */ + virtual bool setRxOffset(double wOffset, size_t chan = 0) = 0; + /** Returns the starting write Timestamp*/ virtual TIMESTAMP initialWriteTimestamp(void)=0; diff --git a/Transceiver52M/device/uhd/UHDDevice.cpp b/Transceiver52M/device/uhd/UHDDevice.cpp index f109660..d8b969a 100644 --- a/Transceiver52M/device/uhd/UHDDevice.cpp +++ b/Transceiver52M/device/uhd/UHDDevice.cpp @@ -557,7 +557,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) #ifdef USE_UHD_3_11 uhd::log::add_logger("OsmoTRX", &uhd_log_handler); uhd::log::set_log_level(uhd::log::debug); - uhd::log::set_console_level(uhd::log::off); + uhd::log::set_console_level(uhd::log::debug); uhd::log::set_logger_level("OsmoTRX", uhd::log::debug); #else uhd::msg::register_handler(&uhd_msg_handler); @@ -870,7 +870,7 @@ int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun, if (rc < 0) { LOGC(DDEV, ERROR) << rx_buffers[0]->str_code(rc); LOGC(DDEV, ERROR) << rx_buffers[0]->str_status(timestamp); - return 0; + return len; } // Receive samples from the usrp until we have enough @@ -975,13 +975,14 @@ int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun bool uhd_device::updateAlignment(TIMESTAMP timestamp) { + aligned = false; return true; } uhd::tune_request_t uhd_device::select_freq(double freq, size_t chan, bool tx) { double rf_spread, rf_freq; - std::vector<double> freqs; + std::vector<tune_result> freqs; uhd::tune_request_t treq(freq); if (dev_type == UMTRX) { @@ -1013,17 +1014,17 @@ uhd::tune_request_t uhd_device::select_freq(double freq, size_t chan, bool tx) freqs = rx_freqs; /* Tune directly if other channel isn't tuned */ - if (freqs[!chan] < 10.0) + if (freqs[!chan].freq < 10.0) return treq; /* Find center frequency between channels */ - rf_spread = fabs(freqs[!chan] - freq); + rf_spread = fabs(freqs[!chan].freq - freq); if (rf_spread > dev_param_map.at(dev_key(B210, tx_sps, rx_sps)).mcr) { LOGC(DDEV, ALERT) << rf_spread << "Hz tuning spread not supported\n"; return treq; } - rf_freq = (freqs[!chan] + freq) / 2.0f; + rf_freq = (freqs[!chan].freq + freq) / 2.0f; treq.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL; treq.target_freq = freq; @@ -1041,11 +1042,13 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx) if (tx) { tres = usrp_dev->set_tx_freq(treq, chan); - tx_freqs[chan] = usrp_dev->get_tx_freq(chan); + tx_freqs[chan].uhd = tres; + tx_freqs[chan].freq = usrp_dev->get_tx_freq(chan); str_dir = "Tx"; } else { tres = usrp_dev->set_rx_freq(treq, chan); - rx_freqs[chan] = usrp_dev->get_rx_freq(chan); + rx_freqs[chan].uhd = tres; + rx_freqs[chan].freq = usrp_dev->get_rx_freq(chan); str_dir = "Rx"; } LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl; @@ -1059,13 +1062,15 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx) */ if (treq.rf_freq_policy == uhd::tune_request_t::POLICY_MANUAL) { if (tx) { - treq = select_freq(tx_freqs[!chan], !chan, true); + treq = select_freq(tx_freqs[!chan].freq, !chan, true); tres = usrp_dev->set_tx_freq(treq, !chan); - tx_freqs[!chan] = usrp_dev->get_tx_freq(!chan); + tx_freqs[chan].uhd = tres; + tx_freqs[!chan].freq = usrp_dev->get_tx_freq(!chan); } else { - treq = select_freq(rx_freqs[!chan], !chan, false); + treq = select_freq(rx_freqs[!chan].freq, !chan, false); tres = usrp_dev->set_rx_freq(treq, !chan); - rx_freqs[!chan] = usrp_dev->get_rx_freq(!chan); + tx_freqs[chan].uhd = tres; + rx_freqs[!chan].freq = usrp_dev->get_rx_freq(!chan); } LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl; @@ -1105,6 +1110,20 @@ bool uhd_device::setTxFreq(double wFreq, size_t chan) return true; } +bool uhd_device::setRxOffset(double wOffset, size_t chan) +{ + uhd::tune_result_t tres; + uhd::tune_request_t treq(rx_freqs[chan].freq - wOffset); + + treq.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + treq.rf_freq = rx_freqs[chan].uhd.actual_rf_freq; + + tres = usrp_dev->set_rx_freq(treq, chan); + rx_freqs[chan].freq = usrp_dev->get_rx_freq(chan); + + return true; +} + bool uhd_device::setRxFreq(double wFreq, size_t chan) { uint16_t req_arfcn; @@ -1116,19 +1135,19 @@ bool uhd_device::setRxFreq(double wFreq, size_t chan) } ScopedLock lock(tune_lock); - req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1); - if (req_arfcn == 0xffff) { - LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz"; - return false; - } - if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) { - LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Rx Frequency " << wFreq - << " Hz (ARFCN " << req_arfcn << " )"; - return false; - } + // req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 1); + // if (req_arfcn == 0xffff) { + // LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Rx Frequency " << wFreq / 1000 << " kHz"; + // return false; + // } + // if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) { + // LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Rx Frequency " << wFreq + // << " Hz (ARFCN " << req_arfcn << " )"; + // return false; + // } - if (!set_band(req_band)) - return false; + // if (!set_band(req_band)) + // return false; return set_freq(wFreq, chan, false); } @@ -1140,7 +1159,7 @@ double uhd_device::getTxFreq(size_t chan) return 0.0; } - return tx_freqs[chan]; + return tx_freqs[chan].freq; } double uhd_device::getRxFreq(size_t chan) @@ -1150,7 +1169,7 @@ double uhd_device::getRxFreq(size_t chan) return 0.0; } - return rx_freqs[chan]; + return rx_freqs[chan].freq; } bool uhd_device::setRxAntenna(const std::string &ant, size_t chan) diff --git a/Transceiver52M/device/uhd/UHDDevice.h b/Transceiver52M/device/uhd/UHDDevice.h index 659fd18..d23911d 100644 --- a/Transceiver52M/device/uhd/UHDDevice.h +++ b/Transceiver52M/device/uhd/UHDDevice.h @@ -80,6 +80,12 @@ struct dev_band_desc { */ class uhd_device : public RadioDevice { public: + + struct tune_result { + uhd::tune_result_t uhd; + double freq; + }; + uhd_device(size_t tx_sps, size_t rx_sps, InterfaceType type, size_t chan_num, double offset, const std::vector<std::string>& tx_paths, @@ -102,6 +108,7 @@ public: bool setTxFreq(double wFreq, size_t chan); bool setRxFreq(double wFreq, size_t chan); + bool setRxOffset(double wOffset, size_t chan); TIMESTAMP initialWriteTimestamp(); TIMESTAMP initialReadTimestamp(); @@ -159,7 +166,7 @@ protected: double rx_gain_min, rx_gain_max; std::vector<double> tx_gains, rx_gains; - std::vector<double> tx_freqs, rx_freqs; + std::vector<tune_result> tx_freqs, rx_freqs; bool band_ass_curr_sess; /* true if "band" was set after last POWEROFF */ enum gsm_band band; struct dev_band_desc band_desc; diff --git a/Transceiver52M/fbsb.cpp b/Transceiver52M/fbsb.cpp new file mode 100644 index 0000000..870b28c --- /dev/null +++ b/Transceiver52M/fbsb.cpp @@ -0,0 +1,148 @@ +#include <atomic> +#include "radioInterface.h" +#include "Interthread.h" +#include "GSMCommon.h" +//#include "Sockets.h" + +#include <sys/types.h> +#include <sys/socket.h> + + +static const auto tssize = 156.25; +static const auto framee_to_read = 12; +static const auto ts_to_read = framee_to_read * 8; +static const int fbsb_chunk_len = ts_to_read * 2 * tssize; // contiguous fcch+sch guaranteed +static const auto corr_offset = 0; + +struct fbsb_par { + + enum class fbsb_state { + IDLE, + INIT, + ACQ, + WAIT, + ACQ_COMPL, + DONE + }; + + int pos; + int time_idx; + std::atomic<fbsb_state> s; + GSM::Time rcvClock[ts_to_read]; + complex fbsb_buf[fbsb_chunk_len+corr_offset]; + complex conjbuf[fbsb_chunk_len+corr_offset]; + complex avgbuf[fbsb_chunk_len+corr_offset]; + void addclk(GSM::Time c) { rcvClock[time_idx] = c; time_idx++; return;} +public: + fbsb_par() : s(fbsb_state::IDLE) { + reset(); + }; + bool done() {return !(time_idx < ts_to_read);} + void take(void* addr, int num_cplx_sps, GSM::Time c) { + memcpy(fbsb_buf+pos+corr_offset,addr, num_cplx_sps * sizeof(complex)); + pos += num_cplx_sps; + assert(pos < fbsb_chunk_len); + rcvClock[time_idx] = c; + time_idx++; + } + void reset() {pos = 0; + time_idx = 0; +// s = fbsb_state::IDLE; + memset(fbsb_buf, 0, sizeof(fbsb_buf)); + memset(conjbuf, 0, sizeof(conjbuf)); + memset(avgbuf, 0, sizeof(avgbuf)); + } + int sz () {return fbsb_chunk_len;} + int off () {return corr_offset;} + + // from osmotrx sigproc + complex fastPeakDetect(complex* rxBurst, float* index, int N) + { + float val, max = 0.0f; + complex amp; + int _index = -1; + + for (size_t i = 0; i < N; i++) { + val = rxBurst[i].abs(); + if (val > max) { + max = val; + _index = i; + amp = rxBurst[i]; + } + } + + if (index) + *index = (float)_index; + + return amp; + } + + void conj_with_lag(complex* in, complex* out, int lag, int offset, int N) { + int total_offset = offset + lag; + for (int s = 0; s < N; s++) + out[s] = in[s + total_offset] * in[s + total_offset - lag].conj(); + } + + auto ac_sum_with_lag(complex* in, int lag, int offset, int N) { + complex v(0,0); + auto total_offset = offset + lag; + for (auto s = 0; s < N; s++) + v += in[s + total_offset] * in[s + total_offset - lag].conj(); + return atan2(v.imag(), v.real()); + } + + + bool running_avg_opt(complex* in, complex* out, int avg_window, int val_to_find, int* idx, int N) { + bool found = false; + complex avg0 = 0; + complex scale(avg_window, avg_window); + for (auto i = 0; i < avg_window; i++) + avg0 += in[i]; + out[0] = avg0 / scale; + + //skip first + for (auto i = 1; i < N - avg_window; i++) { + avg0 += in[i-1] - in[i+avg_window]; + auto tmp = avg0 / scale; + out[i] = tmp; + if (!found && tmp.abs() > val_to_find) { + found = !found; + *idx = i; + return true; + } + } + return false; + } + + int fcch(complex* amp, int* found_idx, bool dump) { + conj_with_lag(fbsb_buf, conjbuf, 3, off(), sz()); + + running_avg_opt(conjbuf, avgbuf, 48, 1.5e6, found_idx, sz()-off()); + float pos; + auto r = fastPeakDetect(avgbuf, &pos, sz()-off()); + *found_idx = *found_idx+off(); + std::cerr << "fcch found at " << pos << " amp: " << r.abs() << std::endl; + *amp = r; + + if(dump) { + { + auto f = fopen("inbuf.cfile", "wb"); + fwrite(fbsb_buf, sz(), 1, f); + fclose(f); + } + { + auto f = fopen("conjbuf.cfile", "wb"); + fwrite(conjbuf, sz(), 1, f); + fclose(f); + } + { + auto f = fopen("schfcch.cfile", "wb"); + fwrite(avgbuf, sz(), 1, f); + fclose(f); + } + exit(0); + } + return pos; + } +}; + diff --git a/Transceiver52M/fbsb.h b/Transceiver52M/fbsb.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Transceiver52M/fbsb.h diff --git a/Transceiver52M/l1if.cpp b/Transceiver52M/l1if.cpp new file mode 100644 index 0000000..440f6e7 --- /dev/null +++ b/Transceiver52M/l1if.cpp @@ -0,0 +1,124 @@ +#include <mutex> +#include <queue> +#include <deque> +#include <condition_variable> +#include <iostream> + +extern "C" { + +#include <unistd.h> +#include <sys/eventfd.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +} +#include "l1if.h" + +using namespace std; +using namespace std::chrono_literals; + +template<typename Data> +class spsc_q{ + + std::queue<Data> m_q; + std::mutex m_mtx; + std::condition_variable m_cond; + bool killme; + +public: + spsc_q() : killme{ false } { } + + void push(Data i){ + std::unique_lock<std::mutex> lock(m_mtx); + m_q.push(i); + m_cond.notify_one(); + } + + Data pop(){ + std::unique_lock<std::mutex> lock(m_mtx); + m_cond.wait_for(lock, 100ms, [&](){ return !m_q.empty() || killme; }); + + if (killme || m_q.empty()){ + return {}; + } + + Data x = m_q.front(); + m_q.pop(); + + return x; + } + + void stop(){ + killme = true; + m_cond.notify_all(); + } + + auto sz() { return m_q.size(); } +}; + + +/* + * trxif_from_trx_c <-> push_c + * trxif_to_trx_c <-> pop_c + * trxif_from_trx_d <-> push_d + * trxif_to_trx_d <-> pop_d + * ... + * + * + */ +class trxl1if { +public: + spsc_q<TRX_C*> c_to_trx; + spsc_q<TRX_C*> c_from_trx; + + spsc_q<trxd_to_trx*> d_to_trx; + spsc_q<trxd_from_trx*> d_from_trx; + + struct osmo_fd g_event_ofd_C; + struct osmo_fd g_event_ofd_D; +}; + +trxl1if trxif; + +void push_c(TRX_C* i) { + uint64_t one = 1; + int rc; + trxif.c_from_trx.push(i); + std::clog << trxif.c_from_trx.sz() << std::endl; + rc = ::write(trxif.g_event_ofd_C.fd, &one, sizeof(one)); + return; +}; +TRX_C* pop_c() { + return trxif.c_to_trx.pop(); +}; +void push_d(trxd_from_trx* i) { + uint64_t one = 1; + int rc; + trxif.d_from_trx.push(i); + rc = ::write(trxif.g_event_ofd_D.fd, &one, sizeof(one)); + return; +}; +trxd_to_trx* pop_d() { + return trxif.d_to_trx.pop(); +}; + +extern "C" { +char* trxif_from_trx_c() { + uint64_t one = 1; + ::read(trxif.g_event_ofd_C.fd, &one, sizeof(one)); + return (char*)trxif.c_from_trx.pop(); +} +void trxif_to_trx_c(char* msg) { + trxif.c_to_trx.push((TRX_C*)msg); +} +trxd_from_trx* trxif_from_trx_d() { + uint64_t one = 1; + ::read(trxif.g_event_ofd_D.fd, &one, sizeof(one)); + return trxif.d_from_trx.pop(); +} +void trxif_to_trx_d(trxd_to_trx* msg) { + trxif.d_to_trx.push(msg); +} +struct osmo_fd* get_c_fd() { return &trxif.g_event_ofd_C;} +struct osmo_fd* get_d_fd() { return &trxif.g_event_ofd_D;} +} diff --git a/Transceiver52M/l1if.h b/Transceiver52M/l1if.h new file mode 100644 index 0000000..d477e47 --- /dev/null +++ b/Transceiver52M/l1if.h @@ -0,0 +1,74 @@ + +#ifdef __cplusplus +extern "C" { +#endif +#include <stdint.h> +#ifdef __cplusplus +} +#endif + +/* ------------------------------------------------------------------------ */ +/* Data interface handlers */ +/* ------------------------------------------------------------------------ */ +/* DATA interface */ +/* */ +/* Messages on the data interface carry one radio burst per UDP message. */ +/* */ +/* Received Data Burst: */ +/* 1 byte timeslot index */ +/* 4 bytes GSM frame number, BE */ +/* 1 byte RSSI in -dBm */ +/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE */ +/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1" */ +/* 2 bytes are not used, but being sent by OsmoTRX */ +/* */ +/* Transmit Data Burst: */ +/* 1 byte timeslot index */ +/* 4 bytes GSM frame number, BE */ +/* 1 byte transmit level wrt ARFCN max, -dB (attenuation) */ +/* 148 bytes output symbol values, 0 & 1 */ +/* ------------------------------------------------------------------------ */ + +struct trxd_to_trx { + uint8_t ts; + uint32_t fn; + uint8_t txlev; + uint8_t symbols[148]; +}; + +struct trxd_from_trx { + uint8_t ts; + uint32_t fn; + uint8_t rssi; + uint16_t toa; + uint8_t symbols[148]; + uint8_t pad[2]; +}; + +#define TRXC_BUF_SIZE 1024 + +struct TRX_C { + char cmd[TRXC_BUF_SIZE]; +}; + +#ifdef __cplusplus +void push_c(TRX_C* i); +TRX_C* pop_c(); + +void push_d(trxd_from_trx* i); +trxd_to_trx* pop_d(); + +#else + + +char* trxif_from_trx_c(); +void trxif_to_trx_c(char* msg); + +struct trxd_from_trx* trxif_from_trx_d(); +void trxif_to_trx_d(struct trxd_to_trx* msg); + +struct osmo_fd* get_c_fd(); +struct osmo_fd* get_d_fd(); + +#endif + diff --git a/Transceiver52M/osmo-trx-org.cpp b/Transceiver52M/osmo-trx-org.cpp new file mode 100644 index 0000000..bd93ab7 --- /dev/null +++ b/Transceiver52M/osmo-trx-org.cpp @@ -0,0 +1,718 @@ +/* + * Copyright (C) 2013 Thomas Tsou <tom@tsou.cc> + * + * SPDX-License-Identifier: LGPL-2.1+ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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. See the GNU + * Lesser General Public License for more details. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "Transceiver.h" +#include "radioDevice.h" +#include "Utils.h" + +#include <time.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <sched.h> +#include <vector> +#include <string> +#include <sstream> +#include <iostream> +#include <sys/signalfd.h> + +#include <GSMCommon.h> +#include <Logger.h> + +extern "C" { +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/stats.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> +#include <osmocom/vty/misc.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/ctrl/control_vty.h> +#include <osmocom/ctrl/ports.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/vty/stats.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/cpu_sched_vty.h> + +#include "convolve.h" +#include "convert.h" +#include "trx_vty.h" +#include "debug.h" +#include "osmo_signal.h" +#include "trx_rate_ctr.h" +} + +#define DEFAULT_CONFIG_FILE "osmo-trx.cfg" + +#define charp2str(a) ((a) ? std::string(a) : std::string("")) + +static char* config_file = (char*)DEFAULT_CONFIG_FILE; + +struct osmo_fd signal_ofd; +volatile bool gshutdown = false; + +static void *tall_trx_ctx; +static struct trx_ctx *g_trx_ctx; +static struct ctrl_handle *g_ctrlh; + +static RadioDevice *usrp; +static RadioInterface *radio; + +/* Create radio interface + * The interface consists of sample rate changes, frequency shifts, + * channel multiplexing, and other conversions. The transceiver core + * accepts input vectors sampled at multiples of the GSM symbol rate. + * The radio interface connects the main transceiver with the device + * object, which may be operating some other rate. + */ +RadioInterface *makeRadioInterface(struct trx_ctx *trx, + RadioDevice *usrp, int type) +{ + RadioInterface *radio = NULL; + size_t div = 1; + int offset = 3; + + if (trx->cfg.ms) { + if (type != RadioDevice::NORMAL) { + LOG(ALERT) << "Unsupported configuration"; + return NULL; + } + + offset *= -1; + } + + switch (type) { + case RadioDevice::NORMAL: + radio = new RadioInterface(usrp, trx->cfg.tx_sps, + trx->cfg.rx_sps, trx->cfg.num_chans, offset, offset); + break; + case RadioDevice::RESAMP_64M: + case RadioDevice::RESAMP_100M: + radio = new RadioInterfaceResamp(usrp, trx->cfg.tx_sps, + trx->cfg.rx_sps); + break; + case RadioDevice::MULTI_ARFCN: + radio = new RadioInterfaceMulti(usrp, trx->cfg.tx_sps, + trx->cfg.rx_sps, trx->cfg.num_chans); + break; + default: + LOG(ALERT) << "Unsupported radio interface configuration"; + return NULL; + } + + if (!radio->init(type)) { + LOG(ALERT) << "Failed to initialize radio interface"; + return NULL; + } + + return radio; +} + +/* Callback function to be called every time we receive a signal from TRANSC */ +static int transc_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + switch (signal) { + case S_MAIN_STOP_REQUIRED: + gshutdown = true; + break; + default: + break; + } + return 0; +} + +/* Create transceiver core + * The multi-threaded modem core operates at multiples of the GSM rate of + * 270.8333 ksps and consists of GSM specific modulation, demodulation, + * and decoding schemes. Also included are the socket interfaces for + * connecting to the upper layer stack. + */ +int makeTransceiver(struct trx_ctx *trx, RadioInterface *radio) +{ + VectorFIFO *fifo; + + transceiver = new Transceiver2(&trx->cfg, GSM::Time(3,0), radio); + if (!transceiver->init()) { + LOG(ALERT) << "Failed to initialize transceiver"; + return -1; + } + + for (size_t i = 0; i < trx->cfg.num_chans; i++) { + fifo = radio->receiveFIFO(i); + if (fifo && transceiver->receiveFIFO(fifo, i)) + continue; + + LOG(ALERT) << "Could not attach FIFO to channel " << i; + return -1; + } + return 0; +} + +static void sig_handler(int signo) +{ + + if (gshutdown) + /* We are in the middle of shutdown process, avoid any kind of extra + action like printing */ + return; + + fprintf(stderr, "signal %d received\n", signo); + switch (signo) { + case SIGINT: + case SIGTERM: + fprintf(stderr, "shutting down\n"); + gshutdown = true; + break; + case SIGABRT: + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. + */ + talloc_report(tall_trx_ctx, stderr); + talloc_report_full(tall_trx_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; + case SIGUSR1: + talloc_report(tall_trx_ctx, stderr); + talloc_report_full(tall_trx_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_trx_ctx, stderr); + break; + case SIGHUP: + log_targets_reopen(); + default: + break; + } + +} + +static int signalfd_callback(struct osmo_fd *ofd, unsigned int what) +{ + struct signalfd_siginfo fdsi; + ssize_t s; + + s = read(ofd->fd, &fdsi, sizeof(struct signalfd_siginfo)); + if (s < 0) { + LOG(FATAL) << "Failed to read from signalfd ("<< ofd->fd << "): " << errno; + gshutdown = true; + return 0; + } + sig_handler(fdsi.ssi_signo); + return 0; +} + +static void setup_signal_handlers() +{ + sigset_t set; + int sfd; + + signal(SIGABRT, &sig_handler); + osmo_init_ignore_signals(); + + /* Other threads created by this thread (main) will inherit a copy of the + signal mask. */ + sigemptyset(&set); + sigaddset(&set, SIGINT); + sigaddset(&set, SIGTERM); + sigaddset(&set, SIGUSR1); + sigaddset(&set, SIGUSR2); + sigaddset(&set, SIGHUP); + if (pthread_sigmask(SIG_BLOCK, &set, NULL)) { + fprintf(stderr, "pthread_sigmask() failed.\n"); + exit(EXIT_FAILURE); + } + + if ((sfd = signalfd(-1, &set, 0)) == -1) { + fprintf(stderr, "signalfd() failed (%d).\n", errno); + exit(EXIT_FAILURE); + } + + osmo_fd_setup(&signal_ofd, sfd, OSMO_FD_READ, signalfd_callback, NULL, 0); + if (osmo_fd_register(&signal_ofd) < 0) { + fprintf(stderr, "osmo_fd_register() failed.\n"); + exit(EXIT_FAILURE); + } +} + +static void print_help() +{ + printf( "Some useful options:\n" + " -h, --help This text\n" + " -C, --config Filename The config file to use\n" + " -V, --version Print the version of OsmoTRX\n" + "\nVTY reference generation:\n" + " --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n" + " --vty-ref-xml Generate the VTY reference XML output and exit.\n" + ); +} + +static void print_deprecated(char opt) +{ + LOG(WARNING) << "Cmd line option '" << opt << "' is deprecated and will be soon removed." + << " Please use VTY cfg option instead." + << " All cmd line options are already being overridden by VTY options if set."; +} + +static void handle_long_options(const char *prog_name, const int long_option) +{ + static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT; + + switch (long_option) { + case 1: + vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg); + if (vty_ref_mode < 0) { + fprintf(stderr, "%s: Unknown VTY reference generation " + "mode '%s'\n", prog_name, optarg); + exit(2); + } + break; + case 2: + fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n", + get_value_string(vty_ref_gen_mode_names, vty_ref_mode), + get_value_string(vty_ref_gen_mode_desc, vty_ref_mode)); + vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode); + exit(0); + default: + fprintf(stderr, "%s: error parsing cmdline options\n", prog_name); + exit(2); + } +} + +static void handle_options(int argc, char **argv, struct trx_ctx* trx) +{ + int option; + unsigned int i; + std::vector<std::string> rx_paths, tx_paths; + bool rx_paths_set = false, tx_paths_set = false; + static int long_option = 0; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"config", 1, 0, 'C'}, + {"version", 0, 0, 'V'}, + {"vty-ref-mode", 1, &long_option, 1}, + {"vty-ref-xml", 0, &long_option, 2}, + {NULL, 0, 0, 0} + }; + + while ((option = getopt_long(argc, argv, "ha:l:i:j:p:c:dmxgfo:s:b:r:A:R:Set:y:z:C:V", long_options, + NULL)) != -1) { + switch (option) { + case 'h': + print_help(); + exit(0); + break; + case 0: + handle_long_options(argv[0], long_option); + break; + case 'a': + print_deprecated(option); + osmo_talloc_replace_string(trx, &trx->cfg.dev_args, optarg); + break; + case 'l': + print_deprecated(option); + log_set_log_level(osmo_stderr_target, atoi(optarg)); + break; + case 'i': + print_deprecated(option); + osmo_talloc_replace_string(trx, &trx->cfg.remote_addr, optarg); + break; + case 'j': + print_deprecated(option); + osmo_talloc_replace_string(trx, &trx->cfg.bind_addr, optarg); + break; + case 'p': + print_deprecated(option); + trx->cfg.base_port = atoi(optarg); + break; + case 'c': + print_deprecated(option); + trx->cfg.num_chans = atoi(optarg); + break; + case 'm': + print_deprecated(option); + trx->cfg.multi_arfcn = true; + break; + case 'x': + print_deprecated(option); + trx->cfg.clock_ref = REF_EXTERNAL; + break; + case 'g': + print_deprecated(option); + trx->cfg.clock_ref = REF_GPS; + break; + case 'f': + print_deprecated(option); + trx->cfg.filler = FILLER_DUMMY; + break; + case 'o': + print_deprecated(option); + trx->cfg.offset = atof(optarg); + break; + case 's': + print_deprecated(option); + trx->cfg.tx_sps = atoi(optarg); + break; + case 'b': + print_deprecated(option); + trx->cfg.rx_sps = atoi(optarg); + break; + case 'r': + print_deprecated(option); + trx->cfg.rtsc = atoi(optarg); + if (!trx->cfg.egprs) /* Don't override egprs which sets different filler */ + trx->cfg.filler = FILLER_NORM_RAND; + break; + case 'A': + print_deprecated(option); + trx->cfg.rach_delay = atoi(optarg); + trx->cfg.filler = FILLER_ACCESS_RAND; + break; + case 'R': + print_deprecated(option); + trx->cfg.rssi_offset = atof(optarg); + trx->cfg.force_rssi_offset = true; + break; + case 'S': + print_deprecated(option); + trx->cfg.swap_channels = true; + break; + case 'e': + print_deprecated(option); + trx->cfg.egprs = true; + break; + case 't': + print_deprecated(option); + trx->cfg.sched_rr = atoi(optarg); + break; + case 'y': + print_deprecated(option); + tx_paths = comma_delimited_to_vector(optarg); + tx_paths_set = true; + break; + case 'z': + print_deprecated(option); + rx_paths = comma_delimited_to_vector(optarg); + rx_paths_set = true; + break; + case 'C': + config_file = optarg; + break; + case 'V': + print_version(1); + exit(0); + break; + default: + goto bad_config; + } + } + + trx->cfg.ms = true; + + if (argc > optind) { + LOG(ERROR) << "Unsupported positional arguments on command line"; + goto bad_config; + } + + /* Cmd line option specific validation & setup */ + + if (trx->cfg.num_chans > TRX_CHAN_MAX) { + LOG(ERROR) << "Too many channels requested, maximum is " << TRX_CHAN_MAX; + goto bad_config; + } + if ((tx_paths_set && tx_paths.size() != trx->cfg.num_chans) || + (rx_paths_set && rx_paths.size() != trx->cfg.num_chans)) { + LOG(ERROR) << "Num of channels and num of Rx/Tx Antennas doesn't match"; + goto bad_config; + } + for (i = 0; i < trx->cfg.num_chans; i++) { + trx->cfg.chans[i].trx = trx; + trx->cfg.chans[i].idx = i; + if (tx_paths_set) + osmo_talloc_replace_string(trx, &trx->cfg.chans[i].tx_path, tx_paths[i].c_str()); + if (rx_paths_set) + osmo_talloc_replace_string(trx, &trx->cfg.chans[i].rx_path, rx_paths[i].c_str()); + } + + return; + +bad_config: + print_help(); + exit(0); +} + +int trx_validate_config(struct trx_ctx *trx) +{ + if (trx->cfg.multi_arfcn && trx->cfg.num_chans > TRX_MCHAN_MAX) { + LOG(ERROR) << "Unsupported number of channels"; + return -1; + } + + /* Force 4 SPS for EDGE or multi-ARFCN configurations */ + if ((trx->cfg.egprs || trx->cfg.multi_arfcn) && + (trx->cfg.tx_sps!=4 || trx->cfg.rx_sps!=4)) { + LOG(ERROR) << "EDGE and Multi-Carrier options require 4 tx and rx sps. Check you config."; + return -1; + } + + return 0; +} + +static int set_sched_rr(unsigned int prio) +{ + struct sched_param param; + int rc; + memset(¶m, 0, sizeof(param)); + param.sched_priority = prio; + LOG(INFO) << "Setting SCHED_RR priority " << param.sched_priority + << ". This setting is DEPRECATED, please use 'policy rr " << param.sched_priority + << "' under the 'sched' VTY node instead."; + rc = sched_setscheduler(getpid(), SCHED_RR, ¶m); + if (rc != 0) { + LOG(ERROR) << "Config: Setting SCHED_RR failed"; + return -1; + } + return 0; +} + +static void print_simd_info(void) +{ +#ifdef HAVE_SSE3 + LOGP(DMAIN, LOGL_INFO, "SSE3 support compiled in"); +#ifdef HAVE___BUILTIN_CPU_SUPPORTS + if (__builtin_cpu_supports("sse3")) + LOGPC(DMAIN, LOGL_INFO, " and supported by CPU\n"); + else + LOGPC(DMAIN, LOGL_INFO, ", but not supported by CPU\n"); +#else + LOGPC(DMAIN, LOGL_INFO, ", but runtime SIMD detection disabled\n"); +#endif +#endif + +#ifdef HAVE_SSE4_1 + LOGP(DMAIN, LOGL_INFO, "SSE4.1 support compiled in"); +#ifdef HAVE___BUILTIN_CPU_SUPPORTS + if (__builtin_cpu_supports("sse4.1")) + LOGPC(DMAIN, LOGL_INFO, " and supported by CPU\n"); + else + LOGPC(DMAIN, LOGL_INFO, ", but not supported by CPU\n"); +#else + LOGPC(DMAIN, LOGL_INFO, ", but runtime SIMD detection disabled\n"); +#endif +#endif + +#ifndef HAVE_ATOMIC_OPS +#pragma message ("Built without atomic operation support. Using Mutex, it may affect performance!") + LOG(NOTICE) << "Built without atomic operation support. Using Mutex, it may affect performance!"; +#endif +} + +static void print_config(struct trx_ctx *trx) +{ + unsigned int i; + std::ostringstream ost(""); + + ost << "Config Settings" << std::endl; + ost << " Log Level............... " << (unsigned int) osmo_stderr_target->loglevel << std::endl; + ost << " Device args............. " << charp2str(trx->cfg.dev_args) << std::endl; + ost << " TRX Base Port........... " << trx->cfg.base_port << std::endl; + ost << " TRX Address............. " << charp2str(trx->cfg.bind_addr) << std::endl; + ost << " GSM BTS Address......... " << charp2str(trx->cfg.remote_addr) << std::endl; + ost << " Channels................ " << trx->cfg.num_chans << std::endl; + ost << " Tx Samples-per-Symbol... " << trx->cfg.tx_sps << std::endl; + ost << " Rx Samples-per-Symbol... " << trx->cfg.rx_sps << std::endl; + ost << " EDGE support............ " << trx->cfg.egprs << std::endl; + ost << " Extended RACH support... " << trx->cfg.ext_rach << std::endl; + ost << " Reference............... " << trx->cfg.clock_ref << std::endl; + ost << " Filler Burst Type....... " << get_value_string(filler_names, trx->cfg.filler) << std::endl; + ost << " Filler Burst TSC........ " << trx->cfg.rtsc << std::endl; + ost << " Filler Burst RACH Delay. " << trx->cfg.rach_delay << std::endl; + ost << " Multi-Carrier........... " << trx->cfg.multi_arfcn << std::endl; + ost << " LO freq. offset......... " << trx->cfg.offset << std::endl; + if (trx->cfg.freq_offset_khz != 0) + ost << " Tune freq. offset....... " << trx->cfg.freq_offset_khz << std::endl; + ost << " RSSI to dBm offset...... " << trx->cfg.rssi_offset << (trx->cfg.force_rssi_offset ? "" : " (relative)") << std::endl; + ost << " Swap channels........... " << trx->cfg.swap_channels << std::endl; + ost << " Tx Antennas............."; + for (i = 0; i < trx->cfg.num_chans; i++) { + std::string p = charp2str(trx->cfg.chans[i].tx_path); + ost << " '" << ((p != "") ? p : "<default>") << "'"; + } + ost << std::endl; + ost << " Rx Antennas............."; + for (i = 0; i < trx->cfg.num_chans; i++) { + std::string p = charp2str(trx->cfg.chans[i].rx_path); + ost << " '" << ((p != "") ? p : "<default>") << "'"; + } + ost << std::endl; + + LOG(INFO) << ost << std::endl; +} + +static void trx_stop() +{ + LOG(NOTICE) << "Shutting down transceiver..." << std::endl; + + delete transceiver; + delete radio; + delete usrp; +} + +static int trx_start(struct trx_ctx *trx) +{ + int type, chans; + unsigned int i; + std::vector<std::string> rx_paths, tx_paths; + RadioDevice::InterfaceType iface = RadioDevice::NORMAL; + + /* Create the low level device object */ + if (trx->cfg.multi_arfcn) + iface = RadioDevice::MULTI_ARFCN; + + /* Generate vector of rx/tx_path: */ + for (i = 0; i < trx->cfg.num_chans; i++) { + rx_paths.push_back(charp2str(trx->cfg.chans[i].rx_path)); + tx_paths.push_back(charp2str(trx->cfg.chans[i].tx_path)); + } + + usrp = RadioDevice::make(trx->cfg.tx_sps, trx->cfg.rx_sps, iface, + trx->cfg.num_chans, trx->cfg.offset, + tx_paths, rx_paths); + type = usrp->open(charp2str(trx->cfg.dev_args), trx->cfg.clock_ref, trx->cfg.swap_channels); + if (type < 0) { + LOG(ALERT) << "Failed to create radio device" << std::endl; + goto shutdown; + } + + /* Setup the appropriate device interface */ + radio = makeRadioInterface(trx, usrp, type); + if (!radio) + goto shutdown; + + /* Create the transceiver core */ + if (makeTransceiver(trx, radio) < 0) + goto shutdown; + + chans = transceiver->numChans(); + LOG(NOTICE) << "-- Transceiver active with " + << chans << " channel(s)" << std::endl; + + return 0; + +shutdown: + trx_stop(); + return -1; +} + +int main(int argc, char *argv[]) +{ + int rc; + + tall_trx_ctx = talloc_named_const(NULL, 0, "OsmoTRX"); + msgb_talloc_ctx_init(tall_trx_ctx, 0); + g_vty_info.tall_ctx = tall_trx_ctx; + + setup_signal_handlers(); + + g_trx_ctx = vty_trx_ctx_alloc(tall_trx_ctx); + + convolve_init(); + convert_init(); + + osmo_init_logging2(tall_trx_ctx, &log_info); + log_enable_multithread(); + osmo_stats_init(tall_trx_ctx); + vty_init(&g_vty_info); + logging_vty_add_cmds(); + ctrl_vty_init(tall_trx_ctx); + osmo_cpu_sched_vty_init(tall_trx_ctx); + trx_vty_init(g_trx_ctx); + + osmo_talloc_vty_add_cmds(); + osmo_stats_vty_add_cmds(); + + handle_options(argc, argv, g_trx_ctx); + + rate_ctr_init(tall_trx_ctx); + + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to open config file: '%s'\n", config_file); + exit(2); + } + + rc = telnet_init_dynif(tall_trx_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_TRX); + if (rc < 0) + exit(1); + + g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_TRX, NULL); + if (!g_ctrlh) { + LOG(ERROR) << "Failed to create CTRL interface.\n"; + exit(1); + } + + /* Backward compatibility: Hack to have 1 channel allocated by default. + * Can be Dropped once we * get rid of "-c" cmdline param */ + if (g_trx_ctx->cfg.num_chans == 0) { + g_trx_ctx->cfg.num_chans = 1; + g_trx_ctx->cfg.chans[0].trx = g_trx_ctx; + g_trx_ctx->cfg.chans[0].idx = 0; + LOG(ERROR) << "No explicit channel config found. Make sure you" \ + " configure channels in VTY config. Using 1 channel as default," \ + " but expect your config to break in the future."; + } + + print_simd_info(); + print_config(g_trx_ctx); + + if (trx_validate_config(g_trx_ctx) < 0) { + LOG(ERROR) << "Config failure - exiting"; + return EXIT_FAILURE; + } + + if (g_trx_ctx->cfg.sched_rr) { + if (set_sched_rr(g_trx_ctx->cfg.sched_rr) < 0) + return EXIT_FAILURE; + } + + osmo_signal_register_handler(SS_MAIN, transc_sig_cb, NULL); + trx_rate_ctr_init(tall_trx_ctx, g_trx_ctx); + + srandom(time(NULL)); + + if(trx_start(g_trx_ctx) < 0) + return EXIT_FAILURE; + + while (!gshutdown) + osmo_select_main(0); + + trx_stop(); + + osmo_fd_unregister(&signal_ofd); + osmo_fd_close(&signal_ofd); + osmo_signal_unregister_handler(SS_MAIN, transc_sig_cb, NULL); + return 0; +} diff --git a/Transceiver52M/osmo-trx.cpp b/Transceiver52M/osmo-trx.cpp index b6f378b..4872f62 100644 --- a/Transceiver52M/osmo-trx.cpp +++ b/Transceiver52M/osmo-trx.cpp @@ -1,8 +1,6 @@ /* * Copyright (C) 2013 Thomas Tsou <tom@tsou.cc> * - * SPDX-License-Identifier: LGPL-2.1+ - * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either @@ -12,27 +10,23 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif -#include "Transceiver.h" +#include "Transceiver2.h" #include "radioDevice.h" -#include "Utils.h" #include <time.h> #include <signal.h> #include <stdlib.h> #include <unistd.h> -#include <getopt.h> -#include <sched.h> -#include <vector> -#include <string> -#include <sstream> -#include <iostream> -#include <sys/signalfd.h> #include <GSMCommon.h> #include <Logger.h> @@ -61,21 +55,101 @@ extern "C" { #include "trx_rate_ctr.h" } -#define DEFAULT_CONFIG_FILE "osmo-trx.cfg" -#define charp2str(a) ((a) ? std::string(a) : std::string("")) +//#include <mempool.cpp> +//atomicstackpool<NUM_ALLOCS,ALLOC_SZ> ALLOCPRIO a0; + +/* Samples-per-symbol for downlink path + * 4 - Uses precision modulator (more computation, less distortion) + * 1 - Uses minimized modulator (less computation, more distortion) + * + * Other values are invalid. Receive path (uplink) is always + * downsampled to 1 sps. Default to 4 sps for all cases except for + * ARM and non-SIMD enabled architectures. + */ +#if defined(HAVE_NEON) || !defined(HAVE_SSE3) +#define DEFAULT_SPS 1 +#else +#define DEFAULT_SPS 4 +#endif + +/* Default configuration parameters + * Note that these values are only used if the particular key does not + * exist in the configuration database. IP port and address values will + * typically be overwritten by the OpenBTS.db values. Other values will + * not be in the database by default. + */ +#define DEFAULT_TRX_PORT 5700 +#define DEFAULT_TRX_IP "127.0.0.1" +#define DEFAULT_EXTREF false +#define DEFAULT_DIVERSITY false +#define DEFAULT_CHANS 1 + +struct trx_config { + std::string log_level; + std::string addr; + std::string dev_args; + unsigned port; + unsigned sps; + unsigned chans; + bool extref; + bool filler; + bool diversity; + bool ms; + double offset; +}; + + + +extern "C" volatile bool gshutdown = false; + + + +/* Setup configuration values + * Don't query the existence of the Log.Level because it's a + * mandatory value. That is, if it doesn't exist, the configuration + * table will crash or will have already crashed. Everything else we + * can survive without and use default values if the database entries + * are empty. + */ +bool trx_setup_config(struct trx_config *config) +{ + std::string refstr, fillstr, divstr, msstr; -static char* config_file = (char*)DEFAULT_CONFIG_FILE; + config->log_level = "foo"; + config->port = DEFAULT_TRX_PORT; + config->addr = DEFAULT_TRX_IP; + //config->extref = DEFAULT_EXTREF; + config->diversity = DEFAULT_DIVERSITY; + config->sps = 4;//DEFAULT_SPS; + config->chans = DEFAULT_CHANS; -struct osmo_fd signal_ofd; -volatile bool gshutdown = false; + /* Diversity only supported on 2 channels */ + if (config->diversity) + config->chans = 2; -static void *tall_trx_ctx; -static struct trx_ctx *g_trx_ctx; -static struct ctrl_handle *g_ctrlh; + refstr = config->extref ? "Enabled" : "Disabled"; + fillstr = config->filler ? "Enabled" : "Disabled"; + divstr = config->diversity ? "Enabled" : "Disabled"; + msstr = config->ms ? "Enabled" : "Disabled"; -static RadioDevice *usrp; -static RadioInterface *radio; + std::ostringstream ost(""); + ost << "Config Settings" << std::endl; + ost << " Log Level............... " << config->log_level << std::endl; + ost << " Device args............. " << config->dev_args << std::endl; + ost << " TRX Base Port........... " << config->port << std::endl; + ost << " TRX Address............. " << config->addr << std::endl; + ost << " Channels................ " << config->chans << std::endl; + ost << " Samples-per-Symbol...... " << config->sps << std::endl; + ost << " External Reference...... " << refstr << std::endl; + ost << " C0 Filler Table......... " << fillstr << std::endl; + ost << " Diversity............... " << divstr << std::endl; + ost << " MS Mode................. " << msstr << std::endl; + ost << " Tuning offset........... " << config->offset << std::endl; + std::cout << ost.str() << std::endl; + + return true; +} /* Create radio interface * The interface consists of sample rate changes, frequency shifts, @@ -84,24 +158,26 @@ static RadioInterface *radio; * The radio interface connects the main transceiver with the device * object, which may be operating some other rate. */ -RadioInterface *makeRadioInterface(struct trx_ctx *trx, +RadioInterface *makeRadioInterface(struct trx_config *config, RadioDevice *usrp, int type) { RadioInterface *radio = NULL; + size_t div = 1; + int offset = 3; + + if (config->ms) { + if (type != RadioDevice::NORMAL) { + LOG(ALERT) << "Unsupported configuration"; + return NULL; + } + + offset *= -1; + } switch (type) { case RadioDevice::NORMAL: - radio = new RadioInterface(usrp, trx->cfg.tx_sps, - trx->cfg.rx_sps, trx->cfg.num_chans); - break; - case RadioDevice::RESAMP_64M: - case RadioDevice::RESAMP_100M: - radio = new RadioInterfaceResamp(usrp, trx->cfg.tx_sps, - trx->cfg.rx_sps); - break; - case RadioDevice::MULTI_ARFCN: - radio = new RadioInterfaceMulti(usrp, trx->cfg.tx_sps, - trx->cfg.rx_sps, trx->cfg.num_chans); + radio = new RadioInterface(usrp, config->sps, config->sps, + config->chans, div, offset); break; default: LOG(ALERT) << "Unsupported radio interface configuration"; @@ -116,590 +192,257 @@ RadioInterface *makeRadioInterface(struct trx_ctx *trx, return radio; } -/* Callback function to be called every time we receive a signal from TRANSC */ -static int transc_sig_cb(unsigned int subsys, unsigned int signal, - void *handler_data, void *signal_data) -{ - switch (signal) { - case S_MAIN_STOP_REQUIRED: - gshutdown = true; - break; - default: - break; - } - return 0; -} - /* Create transceiver core * The multi-threaded modem core operates at multiples of the GSM rate of * 270.8333 ksps and consists of GSM specific modulation, demodulation, * and decoding schemes. Also included are the socket interfaces for * connecting to the upper layer stack. */ -int makeTransceiver(struct trx_ctx *trx, RadioInterface *radio) +Transceiver2 *makeTransceiver(struct trx_config *config, RadioInterface *radio) { + Transceiver2 *trx; VectorFIFO *fifo; - transceiver = new Transceiver(&trx->cfg, GSM::Time(3,0), radio); - if (!transceiver->init()) { + trx = new Transceiver2(config->port, config->addr.c_str(), config->sps, + config->chans, GSM::Time(3,0), radio); + if (!trx->init(config->filler)) { LOG(ALERT) << "Failed to initialize transceiver"; - return -1; + delete trx; + return NULL; } - for (size_t i = 0; i < trx->cfg.num_chans; i++) { + for (size_t i = 0; i < config->chans; i++) { fifo = radio->receiveFIFO(i); - if (fifo && transceiver->receiveFIFO(fifo, i)) + if (fifo && trx->receiveFIFO(fifo, i)) continue; LOG(ALERT) << "Could not attach FIFO to channel " << i; - return -1; - } - return 0; -} - -static void sig_handler(int signo) -{ - - if (gshutdown) - /* We are in the middle of shutdown process, avoid any kind of extra - action like printing */ - return; - - fprintf(stderr, "signal %d received\n", signo); - switch (signo) { - case SIGINT: - case SIGTERM: - fprintf(stderr, "shutting down\n"); - gshutdown = true; - break; - case SIGABRT: - /* in case of abort, we want to obtain a talloc report and - * then run default SIGABRT handler, who will generate coredump - * and abort the process. abort() should do this for us after we - * return, but program wouldn't exit if an external SIGABRT is - * received. - */ - talloc_report(tall_trx_ctx, stderr); - talloc_report_full(tall_trx_ctx, stderr); - signal(SIGABRT, SIG_DFL); - raise(SIGABRT); - break; - case SIGUSR1: - talloc_report(tall_trx_ctx, stderr); - talloc_report_full(tall_trx_ctx, stderr); - break; - case SIGUSR2: - talloc_report_full(tall_trx_ctx, stderr); - break; - case SIGHUP: - log_targets_reopen(); - default: - break; + delete trx; + return NULL; } + return trx; } -static int signalfd_callback(struct osmo_fd *ofd, unsigned int what) +static void sig_handler(int signo) { - struct signalfd_siginfo fdsi; - ssize_t s; - - s = read(ofd->fd, &fdsi, sizeof(struct signalfd_siginfo)); - if (s < 0) { - LOG(FATAL) << "Failed to read from signalfd ("<< ofd->fd << "): " << errno; - gshutdown = true; - return 0; - } - sig_handler(fdsi.ssi_signo); - return 0; + fprintf(stdout, "Received shutdown signal"); + gshutdown = true; } static void setup_signal_handlers() { - sigset_t set; - int sfd; - - signal(SIGABRT, &sig_handler); - osmo_init_ignore_signals(); - - /* Other threads created by this thread (main) will inherit a copy of the - signal mask. */ - sigemptyset(&set); - sigaddset(&set, SIGINT); - sigaddset(&set, SIGTERM); - sigaddset(&set, SIGUSR1); - sigaddset(&set, SIGUSR2); - sigaddset(&set, SIGHUP); - if (pthread_sigmask(SIG_BLOCK, &set, NULL)) { - fprintf(stderr, "pthread_sigmask() failed.\n"); - exit(EXIT_FAILURE); - } - - if ((sfd = signalfd(-1, &set, 0)) == -1) { - fprintf(stderr, "signalfd() failed (%d).\n", errno); + if (signal(SIGINT, sig_handler) == SIG_ERR) { + fprintf(stderr, "Failed to install SIGINT signal handler\n"); exit(EXIT_FAILURE); } - - osmo_fd_setup(&signal_ofd, sfd, OSMO_FD_READ, signalfd_callback, NULL, 0); - if (osmo_fd_register(&signal_ofd) < 0) { - fprintf(stderr, "osmo_fd_register() failed.\n"); - exit(EXIT_FAILURE); + if (signal(SIGTERM, sig_handler) == SIG_ERR) { + fprintf(stderr, "Couldn't install SIGTERM signal handler\n"); + exit( EXIT_FAILURE); } } static void print_help() { - printf( "Some useful options:\n" - " -h, --help This text\n" - " -C, --config Filename The config file to use\n" - " -V, --version Print the version of OsmoTRX\n" - "\nVTY reference generation:\n" - " --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n" - " --vty-ref-xml Generate the VTY reference XML output and exit.\n" - ); + fprintf(stdout, "Options:\n" + " -h This text\n" + " -a UHD device args\n" + " -l Logging level (%s)\n" + " -i IP address of GSM core\n" + " -p Base port number\n" + " -d Enable dual channel diversity receiver\n" + " -x Enable external 10 MHz reference\n" + " -s Samples-per-symbol (1 or 4)\n" + " -c Number of ARFCN channels (default=1)\n" + " -f Enable C0 filler table\n" + " -m Enable MS mode\n" + " -o Set baseband frequency offset (default=auto)\n", + "EMERG, ALERT, CRT, ERR, WARNING, NOTICE, INFO, DEBUG"); } -static void print_deprecated(char opt) +static void handle_options(int argc, char **argv, struct trx_config *config) { - LOG(WARNING) << "Cmd line option '" << opt << "' is deprecated and will be soon removed." - << " Please use VTY cfg option instead." - << " All cmd line options are already being overridden by VTY options if set."; -} + int option; -static void handle_long_options(const char *prog_name, const int long_option) -{ - static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT; - - switch (long_option) { - case 1: - vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg); - if (vty_ref_mode < 0) { - fprintf(stderr, "%s: Unknown VTY reference generation " - "mode '%s'\n", prog_name, optarg); - exit(2); - } - break; - case 2: - fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n", - get_value_string(vty_ref_gen_mode_names, vty_ref_mode), - get_value_string(vty_ref_gen_mode_desc, vty_ref_mode)); - vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode); - exit(0); - default: - fprintf(stderr, "%s: error parsing cmdline options\n", prog_name); - exit(2); - } -} + optind=1; -static void handle_options(int argc, char **argv, struct trx_ctx* trx) -{ - int option; - unsigned int i; - std::vector<std::string> rx_paths, tx_paths; - bool rx_paths_set = false, tx_paths_set = false; - static int long_option = 0; - static struct option long_options[] = { - {"help", 0, 0, 'h'}, - {"config", 1, 0, 'C'}, - {"version", 0, 0, 'V'}, - {"vty-ref-mode", 1, &long_option, 1}, - {"vty-ref-xml", 0, &long_option, 2}, - {NULL, 0, 0, 0} - }; - - while ((option = getopt_long(argc, argv, "ha:l:i:j:p:c:dmxgfo:s:b:r:A:R:Set:y:z:C:V", long_options, - NULL)) != -1) { + config->port = 0; + config->sps = 0; + config->chans = 0; + config->extref = true; + config->filler = false; + config->diversity = false; + config->ms = true; + config->offset = 0.0; + + while ((option = getopt(argc, argv, "xo")) != -1) { switch (option) { - case 'h': - print_help(); - exit(0); - break; - case 0: - handle_long_options(argv[0], long_option); - break; - case 'a': - print_deprecated(option); - osmo_talloc_replace_string(trx, &trx->cfg.dev_args, optarg); - break; - case 'l': - print_deprecated(option); - log_set_log_level(osmo_stderr_target, atoi(optarg)); - break; - case 'i': - print_deprecated(option); - osmo_talloc_replace_string(trx, &trx->cfg.remote_addr, optarg); - break; - case 'j': - print_deprecated(option); - osmo_talloc_replace_string(trx, &trx->cfg.bind_addr, optarg); - break; - case 'p': - print_deprecated(option); - trx->cfg.base_port = atoi(optarg); - break; - case 'c': - print_deprecated(option); - trx->cfg.num_chans = atoi(optarg); - break; - case 'm': - print_deprecated(option); - trx->cfg.multi_arfcn = true; - break; case 'x': - print_deprecated(option); - trx->cfg.clock_ref = REF_EXTERNAL; - break; - case 'g': - print_deprecated(option); - trx->cfg.clock_ref = REF_GPS; - break; - case 'f': - print_deprecated(option); - trx->cfg.filler = FILLER_DUMMY; + config->extref = true; break; case 'o': - print_deprecated(option); - trx->cfg.offset = atof(optarg); - break; - case 's': - print_deprecated(option); - trx->cfg.tx_sps = atoi(optarg); - break; - case 'b': - print_deprecated(option); - trx->cfg.rx_sps = atoi(optarg); - break; - case 'r': - print_deprecated(option); - trx->cfg.rtsc = atoi(optarg); - if (!trx->cfg.egprs) /* Don't override egprs which sets different filler */ - trx->cfg.filler = FILLER_NORM_RAND; - break; - case 'A': - print_deprecated(option); - trx->cfg.rach_delay = atoi(optarg); - trx->cfg.filler = FILLER_ACCESS_RAND; - break; - case 'R': - print_deprecated(option); - trx->cfg.rssi_offset = atof(optarg); - trx->cfg.force_rssi_offset = true; - break; - case 'S': - print_deprecated(option); - trx->cfg.swap_channels = true; - break; - case 'e': - print_deprecated(option); - trx->cfg.egprs = true; - break; - case 't': - print_deprecated(option); - trx->cfg.sched_rr = atoi(optarg); - break; - case 'y': - print_deprecated(option); - tx_paths = comma_delimited_to_vector(optarg); - tx_paths_set = true; - break; - case 'z': - print_deprecated(option); - rx_paths = comma_delimited_to_vector(optarg); - rx_paths_set = true; - break; - case 'C': - config_file = optarg; - break; - case 'V': - print_version(1); - exit(0); + config->offset = atof(optarg); break; default: - goto bad_config; + print_help(); + exit(0); } } - - if (argc > optind) { - LOG(ERROR) << "Unsupported positional arguments on command line"; - goto bad_config; - } - - /* Cmd line option specific validation & setup */ - - if (trx->cfg.num_chans > TRX_CHAN_MAX) { - LOG(ERROR) << "Too many channels requested, maximum is " << TRX_CHAN_MAX; - goto bad_config; - } - if ((tx_paths_set && tx_paths.size() != trx->cfg.num_chans) || - (rx_paths_set && rx_paths.size() != trx->cfg.num_chans)) { - LOG(ERROR) << "Num of channels and num of Rx/Tx Antennas doesn't match"; - goto bad_config; - } - for (i = 0; i < trx->cfg.num_chans; i++) { - trx->cfg.chans[i].trx = trx; - trx->cfg.chans[i].idx = i; - if (tx_paths_set) - osmo_talloc_replace_string(trx, &trx->cfg.chans[i].tx_path, tx_paths[i].c_str()); - if (rx_paths_set) - osmo_talloc_replace_string(trx, &trx->cfg.chans[i].rx_path, rx_paths[i].c_str()); - } - - return; - -bad_config: - print_help(); - exit(0); } -int trx_validate_config(struct trx_ctx *trx) +const char* commands[] { +"CMD POWEROFF", +//"CMD RXTUNE 1782000", +"CMD RXTUNE 931400", +//"CMD TXTUNE 1877000", +"CMD TXTUNE 931900", +"CMD SETTSC 7", +"CMD POWERON", +"CMD SETRXGAIN 30", +"CMD SETPOWER 0", +//"CMD SETSLOT 6 7", +//"CMD SETSLOT 7 13", +//"CMD NOHANDOVER 1 0", +"CMD SYNC", +"CMD GETBSIC", +}; + +const char* commands2[] { +"CMD GETBSIC", +}; + +RadioDevice *usrp; +RadioInterface *radio = NULL; +Transceiver2 *trx = NULL; + + void* tall_trx_ctx; + struct trx_ctx* g_trx_ctx; + +int trx_main(int argc, char *argv[]) { - if (trx->cfg.multi_arfcn && trx->cfg.num_chans > TRX_MCHAN_MAX) { - LOG(ERROR) << "Unsupported number of channels"; - return -1; - } + int type, chans; - /* Force 4 SPS for EDGE or multi-ARFCN configurations */ - if ((trx->cfg.egprs || trx->cfg.multi_arfcn) && - (trx->cfg.tx_sps!=4 || trx->cfg.rx_sps!=4)) { - LOG(ERROR) << "EDGE and Multi-Carrier options require 4 tx and rx sps. Check you config."; - return -1; - } + struct trx_config config; - return 0; -} + std::cerr << "starting.." << std::endl; -static int set_sched_rr(unsigned int prio) -{ - struct sched_param param; - int rc; - memset(¶m, 0, sizeof(param)); - param.sched_priority = prio; - LOG(INFO) << "Setting SCHED_RR priority " << param.sched_priority - << ". This setting is DEPRECATED, please use 'policy rr " << param.sched_priority - << "' under the 'sched' VTY node instead."; - rc = sched_setscheduler(getpid(), SCHED_RR, ¶m); - if (rc != 0) { - LOG(ERROR) << "Config: Setting SCHED_RR failed"; - return -1; - } - return 0; -} + convolve_init(); + convert_init(); -static void print_simd_info(void) -{ -#ifdef HAVE_SSE3 - LOGP(DMAIN, LOGL_INFO, "SSE3 support compiled in"); -#ifdef HAVE___BUILTIN_CPU_SUPPORTS - if (__builtin_cpu_supports("sse3")) - LOGPC(DMAIN, LOGL_INFO, " and supported by CPU\n"); - else - LOGPC(DMAIN, LOGL_INFO, ", but not supported by CPU\n"); -#else - LOGPC(DMAIN, LOGL_INFO, ", but runtime SIMD detection disabled\n"); -#endif -#endif -#ifdef HAVE_SSE4_1 - LOGP(DMAIN, LOGL_INFO, "SSE4.1 support compiled in"); -#ifdef HAVE___BUILTIN_CPU_SUPPORTS - if (__builtin_cpu_supports("sse4.1")) - LOGPC(DMAIN, LOGL_INFO, " and supported by CPU\n"); - else - LOGPC(DMAIN, LOGL_INFO, ", but not supported by CPU\n"); -#else - LOGPC(DMAIN, LOGL_INFO, ", but runtime SIMD detection disabled\n"); -#endif -#endif + tall_trx_ctx = talloc_named_const(NULL, 0, "OsmoTRX"); + msgb_talloc_ctx_init(tall_trx_ctx, 0); + g_vty_info.tall_ctx = tall_trx_ctx; -#ifndef HAVE_ATOMIC_OPS -#pragma message ("Built without atomic operation support. Using Mutex, it may affect performance!") - LOG(NOTICE) << "Built without atomic operation support. Using Mutex, it may affect performance!"; -#endif -} + //setup_signal_handlers(); -static void print_config(struct trx_ctx *trx) -{ - unsigned int i; - std::ostringstream ost(""); + g_trx_ctx = vty_trx_ctx_alloc(tall_trx_ctx); - ost << "Config Settings" << std::endl; - ost << " Log Level............... " << (unsigned int) osmo_stderr_target->loglevel << std::endl; - ost << " Device args............. " << charp2str(trx->cfg.dev_args) << std::endl; - ost << " TRX Base Port........... " << trx->cfg.base_port << std::endl; - ost << " TRX Address............. " << charp2str(trx->cfg.bind_addr) << std::endl; - ost << " GSM BTS Address......... " << charp2str(trx->cfg.remote_addr) << std::endl; - ost << " Channels................ " << trx->cfg.num_chans << std::endl; - ost << " Tx Samples-per-Symbol... " << trx->cfg.tx_sps << std::endl; - ost << " Rx Samples-per-Symbol... " << trx->cfg.rx_sps << std::endl; - ost << " EDGE support............ " << trx->cfg.egprs << std::endl; - ost << " Extended RACH support... " << trx->cfg.ext_rach << std::endl; - ost << " Reference............... " << trx->cfg.clock_ref << std::endl; - ost << " Filler Burst Type....... " << get_value_string(filler_names, trx->cfg.filler) << std::endl; - ost << " Filler Burst TSC........ " << trx->cfg.rtsc << std::endl; - ost << " Filler Burst RACH Delay. " << trx->cfg.rach_delay << std::endl; - ost << " Multi-Carrier........... " << trx->cfg.multi_arfcn << std::endl; - ost << " LO freq. offset......... " << trx->cfg.offset << std::endl; - if (trx->cfg.freq_offset_khz != 0) - ost << " Tune freq. offset....... " << trx->cfg.freq_offset_khz << std::endl; - ost << " RSSI to dBm offset...... " << trx->cfg.rssi_offset << (trx->cfg.force_rssi_offset ? "" : " (relative)") << std::endl; - ost << " Swap channels........... " << trx->cfg.swap_channels << std::endl; - ost << " Tx Antennas............."; - for (i = 0; i < trx->cfg.num_chans; i++) { - std::string p = charp2str(trx->cfg.chans[i].tx_path); - ost << " '" << ((p != "") ? p : "<default>") << "'"; - } - ost << std::endl; - ost << " Rx Antennas............."; - for (i = 0; i < trx->cfg.num_chans; i++) { - std::string p = charp2str(trx->cfg.chans[i].rx_path); - ost << " '" << ((p != "") ? p : "<default>") << "'"; - } - ost << std::endl; + osmo_init_logging2(tall_trx_ctx, &log_info); + log_enable_multithread(); + //osmo_stats_init(tall_trx_ctx); + vty_init(&g_vty_info); + logging_vty_add_cmds(); + //ctrl_vty_init(tall_trx_ctx); + osmo_cpu_sched_vty_init(tall_trx_ctx); + trx_vty_init(g_trx_ctx); - LOG(INFO) << ost << std::endl; -} + osmo_talloc_vty_add_cmds(); + //osmo_stats_vty_add_cmds(); -static void trx_stop() -{ - LOG(NOTICE) << "Shutting down transceiver..." << std::endl; - delete transceiver; - delete radio; - delete usrp; -} + sched_param sch_params; + sch_params.sched_priority = 19; + pthread_setschedparam(pthread_self(), SCHED_RR, &sch_params); -static int trx_start(struct trx_ctx *trx) -{ - int type, chans; - unsigned int i; - std::vector<std::string> rx_paths, tx_paths; - RadioDevice::InterfaceType iface = RadioDevice::NORMAL; + handle_options(argc, argv, &config); - /* Create the low level device object */ - if (trx->cfg.multi_arfcn) - iface = RadioDevice::MULTI_ARFCN; + //setup_signal_handlers(); - /* Generate vector of rx/tx_path: */ - for (i = 0; i < trx->cfg.num_chans; i++) { - rx_paths.push_back(charp2str(trx->cfg.chans[i].rx_path)); - tx_paths.push_back(charp2str(trx->cfg.chans[i].tx_path)); + /* Check database sanity */ + if (!trx_setup_config(&config)) { + std::cerr << "Config: Database failure - exiting" << std::endl; + return EXIT_FAILURE; } - usrp = RadioDevice::make(trx->cfg.tx_sps, trx->cfg.rx_sps, iface, - trx->cfg.num_chans, trx->cfg.offset, - tx_paths, rx_paths); - type = usrp->open(charp2str(trx->cfg.dev_args), trx->cfg.clock_ref, trx->cfg.swap_channels); + //gLogInit("transceiver", config.log_level.c_str(), LOG_LOCAL7); + + srandom(time(NULL)); + + /* Create the low level device object */ + usrp = RadioDevice::make(config.sps, config.sps, RadioDevice::NORMAL, config.chans, + config.offset); + type = usrp->open(config.dev_args, config.extref, false); if (type < 0) { LOG(ALERT) << "Failed to create radio device" << std::endl; goto shutdown; } /* Setup the appropriate device interface */ - radio = makeRadioInterface(trx, usrp, type); + radio = makeRadioInterface(&config, usrp, type); if (!radio) goto shutdown; /* Create the transceiver core */ - if (makeTransceiver(trx, radio) < 0) + trx = makeTransceiver(&config, radio); + if (!trx) goto shutdown; - chans = transceiver->numChans(); - LOG(NOTICE) << "-- Transceiver active with " - << chans << " channel(s)" << std::endl; - - return 0; - -shutdown: - trx_stop(); - return -1; -} - -int main(int argc, char *argv[]) -{ - int rc; - - tall_trx_ctx = talloc_named_const(NULL, 0, "OsmoTRX"); - msgb_talloc_ctx_init(tall_trx_ctx, 0); - g_vty_info.tall_ctx = tall_trx_ctx; - - setup_signal_handlers(); - - g_trx_ctx = vty_trx_ctx_alloc(tall_trx_ctx); - - convolve_init(); - convert_init(); + trx->start(); - osmo_init_logging2(tall_trx_ctx, &log_info); - log_enable_multithread(); - osmo_stats_init(tall_trx_ctx); - vty_init(&g_vty_info); - logging_vty_add_cmds(); - ctrl_vty_init(tall_trx_ctx); - osmo_cpu_sched_vty_init(tall_trx_ctx); - trx_vty_init(g_trx_ctx); - - osmo_talloc_vty_add_cmds(); - osmo_stats_vty_add_cmds(); - - handle_options(argc, argv, g_trx_ctx); - - rate_ctr_init(tall_trx_ctx); - - rc = vty_read_config_file(config_file, NULL); - if (rc < 0) { - fprintf(stderr, "Failed to open config file: '%s'\n", config_file); - exit(2); - } + chans = trx->numChans(); + std::cout << "-- Transceiver active with " + << chans << " channel(s)" << std::endl; - rc = telnet_init_dynif(tall_trx_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_TRX); - if (rc < 0) - exit(1); - g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_TRX, NULL); - if (!g_ctrlh) { - LOG(ERROR) << "Failed to create CTRL interface.\n"; - exit(1); +#if 0 + for(auto i: commands) { + int MAX_PACKET_LENGTH = 100; + char response[MAX_PACKET_LENGTH]; + char buffer[MAX_PACKET_LENGTH]; + memset(response, 0, sizeof(response)); + memset(buffer, 0, sizeof(buffer)); + sprintf(buffer, "%s", i); + trx->commandhandler(buffer, response, 0); + std::cerr << i << "\tr: " << response << " ##" << std::endl; } - /* Backward compatibility: Hack to have 1 channel allocated by default. - * Can be Dropped once we * get rid of "-c" cmdline param */ - if (g_trx_ctx->cfg.num_chans == 0) { - g_trx_ctx->cfg.num_chans = 1; - g_trx_ctx->cfg.chans[0].trx = g_trx_ctx; - g_trx_ctx->cfg.chans[0].idx = 0; - LOG(ERROR) << "No explicit channel config found. Make sure you" \ - " configure channels in VTY config. Using 1 channel as default," \ - " but expect your config to break in the future."; + sleep(30); + + for(auto i: commands2) { + int MAX_PACKET_LENGTH = 100; + char response[MAX_PACKET_LENGTH]; + char buffer[MAX_PACKET_LENGTH]; + memset(response, 0, sizeof(response)); + memset(buffer, 0, sizeof(buffer)); + sprintf(buffer, "%s", i); + trx->commandhandler(buffer, response, 0); + std::cerr << i << "\tr: " << response << " ##" << std::endl; } +#endif - print_simd_info(); - print_config(g_trx_ctx); +// while (!gshutdown) +// sleep(1); - if (trx_validate_config(g_trx_ctx) < 0) { - LOG(ERROR) << "Config failure - exiting"; - return EXIT_FAILURE; - } - - if (g_trx_ctx->cfg.sched_rr) { - if (set_sched_rr(g_trx_ctx->cfg.sched_rr) < 0) - return EXIT_FAILURE; - } +shutdown: +// std::cout << "Shutting down transceiver..." << std::endl; - osmo_signal_register_handler(SS_MAIN, transc_sig_cb, NULL); - trx_rate_ctr_init(tall_trx_ctx, g_trx_ctx); +// delete trx; +// delete radio; +// delete usrp; - srandom(time(NULL)); + return 0; +} - if(trx_start(g_trx_ctx) < 0) - return EXIT_FAILURE; +extern "C" void init_external_transceiver(int argc, char **argv) { + trx_main(argc, argv); +} - while (!gshutdown) - osmo_select_main(0); +extern "C" void stop_trx() { + std::cout << "Shutting down transceiver..." << std::endl; - trx_stop(); + delete trx; + delete radio; + delete usrp; - osmo_fd_unregister(&signal_ofd); - osmo_fd_close(&signal_ofd); - osmo_signal_unregister_handler(SS_MAIN, transc_sig_cb, NULL); - return 0; } diff --git a/Transceiver52M/radioClock.cpp b/Transceiver52M/radioClock.cpp index 2befd0d..61255c0 100644 --- a/Transceiver52M/radioClock.cpp +++ b/Transceiver52M/radioClock.cpp @@ -30,6 +30,41 @@ void RadioClock::set(const GSM::Time& wTime) updateSignal.signal(); } +GSM::Time RadioClock::adjust(GSM::Time &wBase, GSM::Time &wOffset) +{ + int tn_diff, fn_diff = 0; + + /* Modulo TN adustment */ + tn_diff = wBase.TN() + wOffset.TN(); + if (tn_diff < 0) { + tn_diff += 8; + fn_diff--; + } else if (tn_diff >= 8) { + tn_diff -= 8; + fn_diff++; + } + + /* Modulo FN adjustment */ + fn_diff += wBase.FN() + wOffset.FN(); + if (fn_diff < 0) + fn_diff += GSM::gHyperframe; + else if ((unsigned) fn_diff >= GSM::gHyperframe) + fn_diff = fn_diff - GSM::gHyperframe; + + return GSM::Time(fn_diff, tn_diff); +} + +void RadioClock::adjust(GSM::Time& wOffset) +{ + mLock.lock(); + + mClock = adjust(mClock, wOffset); + updateSignal.signal(); + + mLock.unlock(); +} + + void RadioClock::incTN() { ScopedLock lock(mLock); diff --git a/Transceiver52M/radioClock.h b/Transceiver52M/radioClock.h index 472f43d..9dd2e43 100644 --- a/Transceiver52M/radioClock.h +++ b/Transceiver52M/radioClock.h @@ -28,7 +28,10 @@ class RadioClock { public: + static GSM::Time adjust(GSM::Time &base, GSM::Time &offset); + void set(const GSM::Time& wTime); + void adjust(GSM::Time &wOffset); void incTN(); GSM::Time get(); void wait(); diff --git a/Transceiver52M/radioInterface.cpp b/Transceiver52M/radioInterface.cpp index 875245d..70f098e 100644 --- a/Transceiver52M/radioInterface.cpp +++ b/Transceiver52M/radioInterface.cpp @@ -41,7 +41,8 @@ RadioInterface::RadioInterface(RadioDevice *wDevice, size_t tx_sps, int wReceiveOffset, GSM::Time wStartTime) : mDevice(wDevice), mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), underrun(false), overrun(false), writeTimestamp(0), readTimestamp(0), - receiveOffset(wReceiveOffset), mOn(false) + receiveOffset(wReceiveOffset), shiftOffset(0), shiftUpdate(false), + mOn(false) { mClock.set(wStartTime); } @@ -165,11 +166,21 @@ bool RadioInterface::tuneTx(double freq, size_t chan) return mDevice->setTxFreq(freq, chan); } +void RadioInterface::adjustClock(GSM::Time &offset) +{ + mClock.adjust(offset); +} + bool RadioInterface::tuneRx(double freq, size_t chan) { return mDevice->setRxFreq(freq, chan); } +bool RadioInterface::tuneRxOffset(double offset, size_t chan) +{ + return mDevice->setRxOffset(offset, chan); +} + /** synchronization thread loop */ void *AlignRadioServiceLoopAdapter(RadioInterface *radioInterface) { @@ -256,7 +267,10 @@ int RadioInterface::driveReceiveRadio() return -1; GSM::Time rcvClock = mClock.get(); - rcvClock.decTN(receiveOffset); + if (receiveOffset < 0) + rcvClock.incTN(-receiveOffset); + else + rcvClock.decTN(receiveOffset); unsigned tN = rcvClock.TN(); int recvSz = recvBuffer[0]->getAvailSamples(); const int symbolsPerSlot = gSlotLen + 8; @@ -309,6 +323,12 @@ bool RadioInterface::isUnderrun() return retVal; } +void RadioInterface::applyOffset(int offset) +{ + shiftOffset += offset; + shiftUpdate = true; +} + VectorFIFO* RadioInterface::receiveFIFO(size_t chan) { if (chan >= mReceiveFIFO.size()) @@ -341,7 +361,7 @@ int RadioInterface::pullBuffer() numRecv = mDevice->readSamples(convertRecvBuffer, segmentLen, &overrun, - readTimestamp, + readTimestamp + mSPSRx * shiftOffset, &local_underrun); if ((size_t) numRecv != segmentLen) { @@ -376,11 +396,16 @@ bool RadioInterface::pushBuffer() segmentLen * 2); } + if (shiftUpdate) { + mDevice->updateAlignment(0); + shiftUpdate = false; + } + /* Send the all samples in the send buffer */ numSent = mDevice->writeSamples(convertSendBuffer, segmentLen, &local_underrun, - writeTimestamp); + writeTimestamp + mSPSTx * shiftOffset); osmo_trx_sync_or_and_fetch(&underrun, local_underrun); writeTimestamp += numSent; diff --git a/Transceiver52M/radioInterface.h b/Transceiver52M/radioInterface.h index efe5606..f05a376 100644 --- a/Transceiver52M/radioInterface.h +++ b/Transceiver52M/radioInterface.h @@ -56,7 +56,8 @@ protected: RadioClock mClock; ///< the basestation clock! int receiveOffset; ///< offset b/w transmit and receive GSM timestamps, in timeslots - + int shiftOffset; + bool shiftUpdate; bool mOn; ///< indicates radio is on private: @@ -93,6 +94,7 @@ public: /** check for underrun, resets underrun value */ bool isUnderrun(); + void applyOffset(int offset); /** return the receive FIFO */ VectorFIFO* receiveFIFO(size_t chan = 0); @@ -100,12 +102,18 @@ public: /** return the basestation clock */ RadioClock* getClock(void) { return &mClock;}; + /** apply an offset to the main clock */ + void adjustClock(GSM::Time &offset); + /** set transmit frequency */ virtual bool tuneTx(double freq, size_t chan = 0); /** set receive frequency */ virtual bool tuneRx(double freq, size_t chan = 0); + /** set frequency correction */ + virtual bool tuneRxOffset(double offset, size_t chan = 0); + /** set receive gain */ virtual double setRxGain(double dB, size_t chan = 0); diff --git a/Transceiver52M/radioInterfaceMulti.cpp b/Transceiver52M/radioInterfaceMulti.cpp index eaf0886..a419086 100644 --- a/Transceiver52M/radioInterfaceMulti.cpp +++ b/Transceiver52M/radioInterfaceMulti.cpp @@ -258,7 +258,7 @@ int RadioInterfaceMulti::pullBuffer() num = mDevice->readSamples(convertRecvBuffer, outerRecvBuffer->size(), &overrun, - readTimestamp, + readTimestamp + shiftOffset, &local_underrun); if (num != channelizer->inputLen()) { LOG(ALERT) << "Receive error " << num << ", " << channelizer->inputLen(); diff --git a/Transceiver52M/radioInterfaceResamp.cpp b/Transceiver52M/radioInterfaceResamp.cpp index b92432f..d377022 100644 --- a/Transceiver52M/radioInterfaceResamp.cpp +++ b/Transceiver52M/radioInterfaceResamp.cpp @@ -174,7 +174,7 @@ int RadioInterfaceResamp::pullBuffer() num_recv = mDevice->readSamples(convertRecvBuffer, resamp_outchunk, &overrun, - readTimestamp, + readTimestamp + shiftOffset, &local_underrun); if (num_recv != (int) resamp_outchunk) { LOG(ALERT) << "Receive error " << num_recv; diff --git a/Transceiver52M/radioVector.cpp b/Transceiver52M/radioVector.cpp index 68e42c5..2e85d7a 100644 --- a/Transceiver52M/radioVector.cpp +++ b/Transceiver52M/radioVector.cpp @@ -85,6 +85,9 @@ float noiseVector::avg() const { float val = 0.0; + if (!size()) + return 0.0f; + for (size_t i = 0; i < size(); i++) val += (*this)[i]; @@ -93,8 +96,10 @@ float noiseVector::avg() const bool noiseVector::insert(float val) { - if (!size()) - return false; + if (size() < max) { + push_back(val); + return true; + } if (itr >= this->size()) itr = 0; @@ -104,6 +109,16 @@ bool noiseVector::insert(float val) return true; } +bool noiseVector::full() const +{ + return size() >= max; +} + +void noiseVector::reset() +{ + resize(0); +} + GSM::Time VectorQueue::nextTime() const { GSM::Time retVal; diff --git a/Transceiver52M/radioVector.h b/Transceiver52M/radioVector.h index 84e3987..2cb2806 100644 --- a/Transceiver52M/radioVector.h +++ b/Transceiver52M/radioVector.h @@ -52,10 +52,13 @@ class noiseVector : std::vector<float> { public: noiseVector(size_t size = 0); bool insert(float val); + bool full() const; float avg() const; + void reset(); private: size_t itr; + size_t max; }; class VectorFIFO : public InterthreadQueue<radioVector> { }; diff --git a/Transceiver52M/sch.c b/Transceiver52M/sch.c new file mode 100644 index 0000000..5d2b5c6 --- /dev/null +++ b/Transceiver52M/sch.c @@ -0,0 +1,299 @@ +#include <complex.h> +#include <stdio.h> +#include <math.h> +#include <string.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/conv.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/crcgen.h> + +#include "sch.h" + +/* GSM 04.08, 9.1.30 Synchronization channel information */ +struct sch_packed_info { + ubit_t t1_hi[2]; + ubit_t bsic[6]; + ubit_t t1_md[8]; + ubit_t t3p_hi[2]; + ubit_t t2[5]; + ubit_t t1_lo[1]; + ubit_t t3p_lo[1]; +} __attribute__((packed)); + +struct sch_burst { + sbit_t tail0[3]; + sbit_t data0[39]; + sbit_t etsc[64]; + sbit_t data1[39]; + sbit_t tail1[3]; + sbit_t guard[8]; +} __attribute__((packed)); + +static const uint8_t sch_next_output[][2] = { + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, +}; + +static const uint8_t sch_next_state[][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, +}; + +static const struct osmo_conv_code gsm_conv_sch = { + .N = 2, + .K = 5, + .len = GSM_SCH_UNCODED_LEN, + .next_output = sch_next_output, + .next_state = sch_next_state, +}; + +const struct osmo_crc16gen_code gsm0503_sch_crc10 = { + .bits = 10, + .poly = 0x175, + .init = 0x000, + .remainder = 0x3ff, +}; + +#define GSM_MAX_BURST_LEN 157 * 4 +#define GSM_SYM_RATE (1625e3 / 6) * 4 + +/* Pre-generated FCCH measurement tone */ +static complex float fcch_ref[GSM_MAX_BURST_LEN]; + +int float_to_sbit(const float *in, sbit_t *out, float scale, int len) +{ + int i; + + for (i = 0; i < len; i++) { + out[i] = (in[i] - 0.5f) * scale; + } + + return 0; +} + +/* Check if FN contains a FCCH burst */ +int gsm_fcch_check_fn(int fn) +{ + int fn51 = fn % 51; + + switch (fn51) { + case 0: + case 10: + case 20: + case 30: + case 40: + return 1; + } + + return 0; +} + +/* Check if FN contains a SCH burst */ +int gsm_sch_check_fn(int fn) +{ + int fn51 = fn % 51; + + switch (fn51) { + case 1: + case 11: + case 21: + case 31: + case 41: + return 1; + } + + return 0; +} + +/* SCH (T1, T2, T3p) to full FN value */ +int gsm_sch_to_fn(struct sch_info *sch) +{ + int t1 = sch->t1; + int t2 = sch->t2; + int t3p = sch->t3p; + + if ((t1 < 0) || (t2 < 0) || (t3p < 0)) + return -1; + int tt; + int t3 = t3p * 10 + 1; + + if (t3 < t2) + tt = (t3 + 26) - t2; + else + tt = (t3 - t2) % 26; + + return t1 * 51 * 26 + tt * 51 + t3; +} + +/* Parse encoded SCH message */ +int gsm_sch_parse(const uint8_t *info, struct sch_info *desc) +{ + struct sch_packed_info *p = (struct sch_packed_info *) info; + + desc->bsic = (p->bsic[0] << 0) | (p->bsic[1] << 1) | + (p->bsic[2] << 2) | (p->bsic[3] << 3) | + (p->bsic[4] << 4) | (p->bsic[5] << 5); + + desc->t1 = (p->t1_lo[0] << 0) | (p->t1_md[0] << 1) | + (p->t1_md[1] << 2) | (p->t1_md[2] << 3) | + (p->t1_md[3] << 4) | (p->t1_md[4] << 5) | + (p->t1_md[5] << 6) | (p->t1_md[6] << 7) | + (p->t1_md[7] << 8) | (p->t1_hi[0] << 9) | + (p->t1_hi[1] << 10); + + desc->t2 = (p->t2[0] << 0) | (p->t2[1] << 1) | + (p->t2[2] << 2) | (p->t2[3] << 3) | + (p->t2[4] << 4); + + desc->t3p = (p->t3p_lo[0] << 0) | (p->t3p_hi[0] << 1) | + (p->t3p_hi[1] << 2); + + return 0; +} + +/* From osmo-bts */ +int gsm_sch_decode(uint8_t *info, sbit_t *data) +{ + int rc; + ubit_t uncoded[GSM_SCH_UNCODED_LEN]; + + osmo_conv_decode(&gsm_conv_sch, data, uncoded); + + rc = osmo_crc16gen_check_bits(&gsm0503_sch_crc10, + uncoded, GSM_SCH_INFO_LEN, + uncoded + GSM_SCH_INFO_LEN); + if (rc) + return -1; + + memcpy(info, uncoded, GSM_SCH_INFO_LEN * sizeof(ubit_t)); + + return 0; +} + +#define FCCH_TAIL_BITS_LEN 3*4 +#define FCCH_DATA_LEN 100*4// 142 +#if 1 +/* Compute FCCH frequency offset */ +double org_gsm_fcch_offset(float *burst, int len) +{ + int i, start, end; + float a, b, c, d, ang, avg = 0.0f; + double freq; + + if (len > GSM_MAX_BURST_LEN) + len = GSM_MAX_BURST_LEN; + + for (i = 0; i < len; i++) { + a = burst[2 * i + 0]; + b = burst[2 * i + 1]; + c = crealf(fcch_ref[i]); + d = cimagf(fcch_ref[i]); + + burst[2 * i + 0] = a * c - b * d; + burst[2 * i + 1] = a * d + b * c; + } + + start = FCCH_TAIL_BITS_LEN; + end = start + FCCH_DATA_LEN; + + for (i = start; i < end; i++) { + a = cargf(burst[2 * (i - 1) + 0] + + burst[2 * (i - 1) + 1] * I); + b = cargf(burst[2 * i + 0] + + burst[2 * i + 1] * I); + + ang = b - a; + + if (ang > M_PI) + ang -= 2 * M_PI; + else if (ang < -M_PI) + ang += 2 * M_PI; + + avg += ang; + } + + avg /= (float) (end - start); + freq = avg / (2 * M_PI) * GSM_SYM_RATE; + + return freq; +} + + +static const int L1 = 3; +static const int L2 = 32; +static const int N1 = 92; +static const int N2 = 92; + +static struct { int8_t r; int8_t s; } P_inv_table[3+32]; + +void pinv(int P, int8_t* r, int8_t* s, int L1, int L2) { + for (int i = 0; i < L1; i++) + for (int j = 0; j < L2; j++) + if (P == L2 * i - L1 * j) { + *r = i; + *s = j; + return; + } +} + + +float ac_sum_with_lag( complex float* in, int lag, int offset, int N) { + complex float v = 0 + 0*I; + int total_offset = offset + lag; + for (int s = 0; s < N; s++) + v += in[s + total_offset] * conjf(in[s + total_offset - lag]); + return cargf(v); +} + + +double gsm_fcch_offset(float *burst, int len) +{ + int start; + + const float fs = 13. / 48. * 1e6 * 4; + const float expected_fcch_val = ((2 * M_PI) / (fs)) * 67700; + + if (len > GSM_MAX_BURST_LEN) + len = GSM_MAX_BURST_LEN; + + start = FCCH_TAIL_BITS_LEN+10 * 4; + float alpha_one = ac_sum_with_lag((complex float*)burst, L1, start, N1); + float alpha_two = ac_sum_with_lag((complex float*)burst, L2, start, N2); + + float P_unrounded = (L1 * alpha_two - L2 * alpha_one) / (2 * M_PI); + int P = roundf(P_unrounded); + + int8_t r = 0, s = 0; + pinv(P, &r, &s, L1, L2); + + float omegal1 = (alpha_one + 2 * M_PI * r) / L1; + float omegal2 = (alpha_two + 2 * M_PI * s) / L2; + + float rv = org_gsm_fcch_offset(burst, len); + //return rv; + + float reval = GSM_SYM_RATE / (2 * M_PI) * (expected_fcch_val - (omegal1+omegal2)/2); + //fprintf(stderr, "XX rv %f %f %f %f\n", rv, reval, omegal1 / (2 * M_PI) * fs, omegal2 / (2 * M_PI) * fs); + + //fprintf(stderr, "XX rv %f %f\n", rv, reval); + + return -reval; +} +#endif +/* Generate FCCH measurement tone */ +static __attribute__((constructor)) void init() +{ + int i; + double freq = 0.25; + + for (i = 0; i < GSM_MAX_BURST_LEN; i++) { + fcch_ref[i] = sin(2 * M_PI * freq * (double) i) + + cos(2 * M_PI * freq * (double) i) * I; + } + +} diff --git a/Transceiver52M/sch.h b/Transceiver52M/sch.h new file mode 100644 index 0000000..445792b --- /dev/null +++ b/Transceiver52M/sch.h @@ -0,0 +1,27 @@ +#ifndef _SCH_H_ +#define _SCH_H_ + +#include <osmocom/core/bits.h> + +struct sch_info { + int bsic; + int t1; + int t2; + int t3p; +}; + +#define GSM_SCH_INFO_LEN 25 +#define GSM_SCH_UNCODED_LEN 35 +#define GSM_SCH_CODED_LEN 78 + +int gsm_sch_decode(uint8_t *sb_info, sbit_t *burst); +int gsm_sch_parse(const uint8_t *sb_info, struct sch_info *desc); +int gsm_sch_to_fn(struct sch_info *sch); +int gsm_sch_check_fn(int fn); +int gsm_fcch_check_fn(int fn); + +double gsm_fcch_offset(float *burst, int len); + +int float_to_sbit(const float *in, sbit_t *out, float scale, int len); + +#endif /* _SCH_H_ */ diff --git a/Transceiver52M/sigProcLib.cpp b/Transceiver52M/sigProcLib.cpp index df87f94..792c781 100644 --- a/Transceiver52M/sigProcLib.cpp +++ b/Transceiver52M/sigProcLib.cpp @@ -98,6 +98,7 @@ struct CorrelationSequence { signalVector *sequence; void *buffer; + void *history; float toa; complex gain; }; @@ -129,6 +130,7 @@ struct PulseSequence { static CorrelationSequence *gMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; static CorrelationSequence *gEdgeMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; static CorrelationSequence *gRACHSequences[] = {NULL,NULL,NULL}; +static CorrelationSequence *gSCHSequence = NULL; static PulseSequence *GSMPulse1 = NULL; static PulseSequence *GSMPulse4 = NULL; @@ -151,6 +153,9 @@ void sigProcLibDestroy() gRACHSequences[i] = NULL; } + delete gSCHSequence; + gSCHSequence = NULL; + delete GMSKRotation1; delete GMSKReverseRotation1; delete GMSKRotation4; @@ -315,6 +320,8 @@ static signalVector *convolve(const signalVector *x, const signalVector *h, append = true; break; case CUSTOM: + + // FIXME: x->getstart? if (start < h->size() - 1) { head = h->size() - start; append = true; @@ -1384,6 +1391,70 @@ release: return status; } +bool generateSCHSequence(int sps) +{ + bool status = true; + float toa; + complex *data = NULL; + signalVector *autocorr = NULL; + signalVector *seq0 = NULL, *seq1 = NULL, *_seq1 = NULL; + + delete gSCHSequence; + + seq0 = modulateBurst(gSCHSynchSequence, 0, sps, false); + if (!seq0) + return false; + + seq1 = modulateBurst(gSCHSynchSequence, 0, sps, true); + if (!seq1) { + status = false; + goto release; + } + + conjugateVector(*seq1); + + /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */ + data = (complex *) convolve_h_alloc(seq1->size()); + _seq1 = new signalVector(data, 0, seq1->size()); + _seq1->setAligned(true); + memcpy(_seq1->begin(), seq1->begin(), seq1->size() * sizeof(complex)); + + autocorr = convolve(seq0, _seq1, autocorr, NO_DELAY); + if (!autocorr) { + status = false; + goto release; + } + + gSCHSequence = new CorrelationSequence; + gSCHSequence->sequence = _seq1; + gSCHSequence->buffer = data; + gSCHSequence->gain = peakDetect(*autocorr, &toa, NULL); + gSCHSequence->history = new complex[_seq1->size()]; + + /* For 1 sps only + * (Half of correlation length - 1) + midpoint of pulse shaping filer + * 20.5 = (64 / 2 - 1) + 1.5 + */ + if (sps == 1) + gSCHSequence->toa = toa - 32.5; + else + gSCHSequence->toa = 0.0; + +release: + delete autocorr; + delete seq0; + delete seq1; + + if (!status) { + delete _seq1; + free(data); + gSCHSequence = NULL; + } + + return status; +} + + /* * Peak-to-average computation +/- range from peak in symbols */ @@ -1469,6 +1540,9 @@ static float computeCI(const signalVector *burst, const CorrelationSequence *syn /* Integer position where the sequence starts */ const int ps = start + 1 - N + (int)roundf(toa); + if(ps < 0) // might be -22 for toa 40 with N=64, if off by a lot during sch ms sync + return 0; + /* Estimate Signal power */ S = 0.0f; for (int i=0, j=ps; i<(int)N; i++,j++) @@ -1652,6 +1726,66 @@ static int detectRACHBurst(const signalVector &burst, float threshold, int sps, return rc; } +int detectSCHBurst(signalVector &burst, + float thresh, + int sps, + sch_detect_type state, struct estim_burst_params *ebp) +{ + int rc, start, target, head, tail, len; + float _toa; + complex _amp; + signalVector *corr, *_burst; + CorrelationSequence *sync; + + if ((sps != 1) && (sps != 4)) + return -1; + + target = 3 + 39 + 64; + + switch (state) { + case sch_detect_type::SCH_DETECT_NARROW: + head = 4; + tail = 4; + break; + case sch_detect_type::SCH_DETECT_FULL: + default: + head = target - 1; + tail = 39 + 3 + 9; + break; + } + + start = (target - head) * 1 - 1; + len = (head + tail) * 1; + sync = gSCHSequence; + corr = new signalVector(len); + + _burst = new signalVector(burst, sync->sequence->size(), 5); + + memcpy(_burst->begin() - sync->sequence->size(), sync->history, + sync->sequence->size() * sizeof(complex)); + + memcpy(sync->history, &burst.begin()[burst.size() - sync->sequence->size()], + sync->sequence->size() * sizeof(complex)); + + rc = detectBurst(*_burst, *corr, sync, + thresh, sps, start, len, ebp); + delete corr; + + if (rc < 0) { + return -1; + } else if (!rc) { + ebp->amp = 0.0f; + ebp->toa = 0.0f; + return 0; + } + + /* Subtract forward search bits from delay */ + ebp->toa = ebp->toa - head; + + return rc; +} + + /* * Normal burst detection * @@ -1670,7 +1804,7 @@ static int analyzeTrafficBurst(const signalVector &burst, unsigned tsc, float th return -SIGERR_UNSUPPORTED; target = 3 + 58 + 16 + 5; - head = 6; + head = 10; tail = 6 + max_toa; sync = gMidambles[tsc]; @@ -1921,6 +2055,8 @@ bool sigProcLibSetup() generateRACHSequence(&gRACHSequences[1], gRACHSynchSequenceTS1, 1); generateRACHSequence(&gRACHSequences[2], gRACHSynchSequenceTS2, 1); + generateSCHSequence(1); + for (int tsc = 0; tsc < 8; tsc++) { generateMidamble(1, tsc); gEdgeMidambles[tsc] = generateEdgeMidamble(tsc); diff --git a/Transceiver52M/sigProcLib.h b/Transceiver52M/sigProcLib.h index 0c3c7c6..73d4704 100644 --- a/Transceiver52M/sigProcLib.h +++ b/Transceiver52M/sigProcLib.h @@ -31,6 +31,7 @@ enum CorrType{ TSC, ///< timeslot should contain a normal burst EXT_RACH, ///< timeslot should contain an extended access burst RACH, ///< timeslot should contain an access burst + SCH, EDGE, ///< timeslot should contain an EDGE burst IDLE ///< timeslot is an idle (or dummy) burst }; @@ -133,6 +134,16 @@ int detectAnyBurst(const signalVector &burst, unsigned max_toa, struct estim_burst_params *ebp); +enum class sch_detect_type { + SCH_DETECT_FULL, + SCH_DETECT_NARROW, +}; + +int detectSCHBurst(signalVector &rxBurst, + float detectThreshold, + int sps, + sch_detect_type state, struct estim_burst_params *ebp); + /** Demodulate burst basde on type and output soft bits */ SoftVector *demodAnyBurst(const signalVector &burst, CorrType type, int sps, struct estim_burst_params *ebp); |