diff options
Diffstat (limited to 'Transceiver52M')
84 files changed, 6758 insertions, 790 deletions
diff --git a/Transceiver52M/ChannelizerBase.cpp b/Transceiver52M/ChannelizerBase.cpp index e6eeed2..9910091 100644 --- a/Transceiver52M/ChannelizerBase.cpp +++ b/Transceiver52M/ChannelizerBase.cpp @@ -244,6 +244,7 @@ ChannelizerBase::~ChannelizerBase() free(subFilters[i]); delete[] hist[i]; } + free(subFilters); fft_free(fftInput); fft_free(fftOutput); diff --git a/Transceiver52M/Complex.h b/Transceiver52M/Complex.h index 6e72346..597a26f 100644 --- a/Transceiver52M/Complex.h +++ b/Transceiver52M/Complex.h @@ -29,7 +29,7 @@ unlike the built-in complex<> templates, these inline most operations for speed template<class Real> class Complex { public: - + typedef Real value_type; Real r, i; /**@name constructors */ diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am index 7dad159..0b63b16 100644 --- a/Transceiver52M/Makefile.am +++ b/Transceiver52M/Makefile.am @@ -40,7 +40,9 @@ COMMON_SOURCES = \ ChannelizerBase.cpp \ Channelizer.cpp \ Synthesis.cpp \ - proto_trxd.c + proto_trxd.c \ + grgsm_vitac/grgsm_vitac.cpp \ + grgsm_vitac/viterbi_detector.cc libtransceiver_common_la_SOURCES = \ $(COMMON_SOURCES) \ @@ -73,6 +75,47 @@ COMMON_LDADD = \ $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOVTY_LIBS) +if ENABLE_MS_TRX +AM_CPPFLAGS += -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/ +AM_CPPFLAGS += -I${srcdir} + +TRXCON_LDADD = \ + $(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libtrxcon.a \ + $(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1sched.a \ + $(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1gprs.a \ + $(LIBOSMOCODING_LIBS) + +MS_LOWER_SRC = \ + ms/sch.c \ + ms/ms.cpp \ + ms/threadsched.cpp \ + ms/ms_rx_lower.cpp \ + grgsm_vitac/grgsm_vitac.cpp \ + grgsm_vitac/viterbi_detector.cc + +MS_UPPER_SRC = \ + ms/ms_upper.cpp \ + ms/l1ctl_server.c \ + ms/logging.c \ + ms/l1ctl_server_cb.cpp \ + ms/ms_trxcon_if.cpp + +noinst_HEADERS += \ + ms/ms.h \ + ms/threadsched.h \ + ms/bladerf_specific.h \ + ms/uhd_specific.h \ + ms/ms_upper.h \ + ms/ms_trxcon_if.h \ + ms/itrq.h \ + ms/sch.h \ + ms/threadpool.h \ + grgsm_vitac/viterbi_detector.h \ + grgsm_vitac/constants.h \ + grgsm_vitac/grgsm_vitac.h + +endif + bin_PROGRAMS = if DEVICE_UHD @@ -83,6 +126,17 @@ osmo_trx_uhd_LDADD = \ $(COMMON_LDADD) \ $(UHD_LIBS) osmo_trx_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) + +#if ENABLE_MS_TRX +#bin_PROGRAMS += osmo-trx-ms-uhd +#osmo_trx_ms_uhd_SOURCES = $(MS_LOWER_SRC) $(MS_UPPER_SRC) +#osmo_trx_ms_uhd_LDADD = \ +# $(builddir)/device/uhd/libdevice.la \ +# $(COMMON_LDADD) \ +# $(UHD_LIBS) \ +# $(TRXCON_LDADD) +#osmo_trx_ms_uhd_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DBUILDUHD +#endif endif if DEVICE_USRP1 @@ -105,6 +159,27 @@ osmo_trx_lms_LDADD = \ osmo_trx_lms_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS) endif +if DEVICE_BLADE +bin_PROGRAMS += osmo-trx-blade +osmo_trx_blade_SOURCES = osmo-trx.cpp +osmo_trx_blade_LDADD = \ + $(builddir)/device/bladerf/libdevice.la \ + $(COMMON_LDADD) \ + $(BLADE_LIBS) +osmo_trx_blade_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS) + +if ENABLE_MS_TRX +bin_PROGRAMS += osmo-trx-ms-blade +osmo_trx_ms_blade_SOURCES = $(MS_LOWER_SRC) $(MS_UPPER_SRC) +osmo_trx_ms_blade_LDADD = \ + $(builddir)/device/bladerf/libdevice.la \ + $(COMMON_LDADD) \ + $(BLADE_LIBS) \ + $(TRXCON_LDADD) +osmo_trx_ms_blade_CPPFLAGS = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DBUILDBLADE +endif +endif + if DEVICE_IPC bin_PROGRAMS += osmo-trx-ipc osmo_trx_ipc_SOURCES = osmo-trx.cpp @@ -113,4 +188,3 @@ osmo_trx_ipc_LDADD = \ $(COMMON_LDADD) osmo_trx_ipc_CPPFLAGS = $(AM_CPPFLAGS) endif - diff --git a/Transceiver52M/Resampler.cpp b/Transceiver52M/Resampler.cpp index 2ca6406..841c3a9 100644 --- a/Transceiver52M/Resampler.cpp +++ b/Transceiver52M/Resampler.cpp @@ -13,10 +13,6 @@ * 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 */ #include <stdlib.h> @@ -36,7 +32,7 @@ extern "C" { #define M_PI 3.14159265358979323846264338327f #endif -#define MAX_OUTPUT_LEN 4096 +#define MAX_OUTPUT_LEN 4096*4 using namespace std; diff --git a/Transceiver52M/Resampler.h b/Transceiver52M/Resampler.h index 5af8d26..e1962c7 100644 --- a/Transceiver52M/Resampler.h +++ b/Transceiver52M/Resampler.h @@ -13,10 +13,6 @@ * 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 */ #ifndef _RESAMPLER_H_ diff --git a/Transceiver52M/Transceiver.cpp b/Transceiver52M/Transceiver.cpp index 676ffde..f5ddd9b 100644 --- a/Transceiver52M/Transceiver.cpp +++ b/Transceiver52M/Transceiver.cpp @@ -29,6 +29,7 @@ #include <fstream> #include "Transceiver.h" #include <Logger.h> +#include <grgsm_vitac/grgsm_vitac.h> extern "C" { #include "osmo_signal.h" @@ -132,26 +133,22 @@ bool TransceiverState::init(FillerType filler, size_t sps, float scale, size_t r return false; } -Transceiver::Transceiver(int wBasePort, - const char *TRXAddress, - const char *GSMcoreAddress, - size_t tx_sps, size_t rx_sps, size_t chans, +Transceiver::Transceiver(const struct trx_cfg *cfg, GSM::Time wTransmitLatency, - RadioInterface *wRadioInterface, - double wRssiOffset, int wStackSize) - : mBasePort(wBasePort), mLocalAddr(TRXAddress), mRemoteAddr(GSMcoreAddress), - mClockSocket(-1), mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface), - rssiOffset(wRssiOffset), stackSize(wStackSize), - mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mExtRACH(false), mEdge(false), - mOn(false), mForceClockInterface(false), - mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), mMaxExpectedDelayNB(0), - mWriteBurstToDiskMask(0) + RadioInterface *wRadioInterface) + : mChans(cfg->num_chans), cfg(cfg), + mCtrlSockets(mChans), mClockSocket(-1), + mTxPriorityQueues(mChans), mReceiveFIFO(mChans), + mRxServiceLoopThreads(mChans), mRxLowerLoopThread(nullptr), mTxLowerLoopThread(nullptr), + mTxPriorityQueueServiceLoopThreads(mChans), mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface), + mOn(false),mForceClockInterface(false), mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), + mMaxExpectedDelayNB(0), mWriteBurstToDiskMask(0), mVersionTRXD(mChans), mStates(mChans) { txFullScale = mRadioInterface->fullScaleInputValue(); rxFullScale = mRadioInterface->fullScaleOutputValue(); - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 8; j++) + for (size_t i = 0; i < ARRAY_SIZE(mHandover); i++) { + for (size_t j = 0; j < ARRAY_SIZE(mHandover[i]); j++) mHandover[i][j] = false; } } @@ -199,11 +196,9 @@ int Transceiver::ctrl_sock_cb(struct osmo_fd *bfd, unsigned int flags) * are still expected to report clock indications through control channel * activity. */ -bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay, - bool edge, bool ext_rach) +bool Transceiver::init() { int d_srcport, d_dstport, c_srcport, c_dstport; - if (!mChans) { LOG(FATAL) << "No channels assigned"; return false; @@ -214,41 +209,34 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay, return false; } - mExtRACH = ext_rach; - mEdge = edge; + initvita(); mDataSockets.resize(mChans, -1); - mCtrlSockets.resize(mChans); - mTxPriorityQueueServiceLoopThreads.resize(mChans); - mRxServiceLoopThreads.resize(mChans); - mTxPriorityQueues.resize(mChans); - mReceiveFIFO.resize(mChans); - mStates.resize(mChans); - mVersionTRXD.resize(mChans); /* Filler table retransmissions - support only on channel 0 */ - if (filler == FILLER_DUMMY) + if (cfg->filler == FILLER_DUMMY) mStates[0].mRetrans = true; /* Setup sockets */ mClockSocket = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, - mLocalAddr.c_str(), mBasePort, - mRemoteAddr.c_str(), mBasePort + 100, + cfg->bind_addr, cfg->base_port, + cfg->remote_addr, cfg->base_port + 100, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); if (mClockSocket < 0) return false; for (size_t i = 0; i < mChans; i++) { int rv; - c_srcport = mBasePort + 2 * i + 1; - c_dstport = mBasePort + 2 * i + 101; - d_srcport = mBasePort + 2 * i + 2; - d_dstport = mBasePort + 2 * i + 102; + FillerType filler = cfg->filler; + c_srcport = cfg->base_port + 2 * i + 1; + c_dstport = cfg->base_port + 2 * i + 101; + d_srcport = cfg->base_port + 2 * i + 2; + d_dstport = cfg->base_port + 2 * i + 102; rv = osmo_sock_init2_ofd(&mCtrlSockets[i].conn_bfd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, - mLocalAddr.c_str(), c_srcport, - mRemoteAddr.c_str(), c_dstport, + cfg->bind_addr, c_srcport, + cfg->remote_addr, c_dstport, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); if (rv < 0) return false; @@ -258,8 +246,8 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay, mDataSockets[i] = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, - mLocalAddr.c_str(), d_srcport, - mRemoteAddr.c_str(), d_dstport, + cfg->bind_addr, d_srcport, + cfg->remote_addr, d_dstport, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); if (mDataSockets[i] < 0) return false; @@ -267,7 +255,7 @@ bool Transceiver::init(FillerType filler, size_t rtsc, unsigned rach_delay, if (i && filler == FILLER_DUMMY) filler = FILLER_ZERO; - mStates[i].init(filler, mSPSTx, txFullScale, rtsc, rach_delay); + mStates[i].init(filler, cfg->tx_sps, txFullScale, cfg->rtsc, cfg->rach_delay); } /* Randomize the central clock */ @@ -309,8 +297,8 @@ bool Transceiver::start() } /* Device is running - launch I/O threads */ - mRxLowerLoopThread = new Thread(stackSize); - mTxLowerLoopThread = new Thread(stackSize); + mRxLowerLoopThread = new Thread(cfg->stack_size); + mTxLowerLoopThread = new Thread(cfg->stack_size); mTxLowerLoopThread->start((void * (*)(void*)) TxLowerLoopAdapter,(void*) this); mRxLowerLoopThread->start((void * (*)(void*)) @@ -321,14 +309,14 @@ bool Transceiver::start() TrxChanThParams *params = (TrxChanThParams *)malloc(sizeof(struct TrxChanThParams)); params->trx = this; params->num = i; - mRxServiceLoopThreads[i] = new Thread(stackSize); + mRxServiceLoopThreads[i] = new Thread(cfg->stack_size); mRxServiceLoopThreads[i]->start((void * (*)(void*)) RxUpperLoopAdapter, (void*) params); params = (TrxChanThParams *)malloc(sizeof(struct TrxChanThParams)); params->trx = this; params->num = i; - mTxPriorityQueueServiceLoopThreads[i] = new Thread(stackSize); + mTxPriorityQueueServiceLoopThreads[i] = new Thread(cfg->stack_size); mTxPriorityQueueServiceLoopThreads[i]->start((void * (*)(void*)) TxUpperLoopAdapter, (void*) params); } @@ -401,11 +389,11 @@ void Transceiver::addRadioVector(size_t chan, BitVector &bits, /* Use the number of bits as the EDGE burst indicator */ if (bits.size() == EDGE_BURST_NBITS) - burst = modulateEdgeBurst(bits, mSPSTx); + burst = modulateEdgeBurst(bits, cfg->tx_sps); else - burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), mSPSTx); + burst = modulateBurst(bits, 8 + (wTime.TN() % 4 == 0), cfg->tx_sps); - scaleVector(*burst, txFullScale * pow(10, -RSSI / 10)); + scaleVector(*burst, txFullScale * pow(10, (double) -RSSI / 20)); radio_burst = new radioVector(wTime, burst); @@ -566,16 +554,16 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime, break; case IV: case VI: - return mExtRACH ? EXT_RACH : RACH; + return cfg->ext_rach ? EXT_RACH : RACH; break; case V: { int mod51 = burstFN % 51; if ((mod51 <= 36) && (mod51 >= 14)) - return mExtRACH ? EXT_RACH : RACH; + return cfg->ext_rach ? EXT_RACH : RACH; else if ((mod51 == 4) || (mod51 == 5)) - return mExtRACH ? EXT_RACH : RACH; + return cfg->ext_rach ? EXT_RACH : RACH; else if ((mod51 == 45) || (mod51 == 46)) - return mExtRACH ? EXT_RACH : RACH; + return cfg->ext_rach ? EXT_RACH : RACH; else if (mHandover[burstTN][sdcch4_subslot[burstFN % 102]]) return RACH; else @@ -593,11 +581,11 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime, case XIII: { int mod52 = burstFN % 52; if ((mod52 == 12) || (mod52 == 38)) - return mExtRACH ? EXT_RACH : RACH; + return RACH; /* RACH is always 8-bit on PTCCH/U */ else if ((mod52 == 25) || (mod52 == 51)) return IDLE; else /* Enable 8-PSK burst detection if EDGE is enabled */ - return mEdge ? EDGE : TSC; + return cfg->egprs ? EDGE : TSC; break; } case LOOPBACK: @@ -622,6 +610,51 @@ void writeToFile(radioVector *radio_burst, size_t chan) outfile.close(); } +double Transceiver::rssiOffset(size_t chan) +{ + if (cfg->force_rssi_offset) + return cfg->rssi_offset; + return mRadioInterface->rssiOffset(chan) + cfg->rssi_offset; +} + +static SoftVector *demodAnyBurst_va(const signalVector &burst, CorrType type, int sps, int rach_max_toa, int tsc) +{ + auto conved_beg = reinterpret_cast<const std::complex<float> *>(&burst.begin()[0]); + std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR]; + float ncmax; + const unsigned burst_len_bits = 148 + 8; + char demodded_softbits[burst_len_bits]; + SoftVector *bits = new SoftVector(burst_len_bits); + + if (type == CorrType::TSC) { + auto rach_burst_start = get_norm_chan_imp_resp(conved_beg, chan_imp_resp, &ncmax, tsc); + rach_burst_start = std::max(rach_burst_start, 0); + detect_burst_nb(conved_beg, chan_imp_resp, rach_burst_start, demodded_softbits); + } else { + auto normal_burst_start = get_access_imp_resp(conved_beg, chan_imp_resp, &ncmax, 0); + normal_burst_start = std::max(normal_burst_start, 0); + detect_burst_ab(conved_beg, chan_imp_resp, normal_burst_start, demodded_softbits, rach_max_toa); + } + + float *s = &bits->begin()[0]; + for (unsigned int i = 0; i < 148; i++) + s[i] = demodded_softbits[i] * -1; + for (unsigned int i = 148; i < burst_len_bits; i++) + s[i] = 0; + return bits; +} + +#define USE_VA + +#ifdef USE_VA +// signalvector is owning despite claiming not to, but we can pretend, too.. +static void dummy_free(void *wData){}; +static void *dummy_alloc(size_t newSize) +{ + return 0; +}; +#endif + /* * Pull bursts from the FIFO and handle according to the slot * and burst correlation type. Equalzation is currently disabled. @@ -641,6 +674,10 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) SoftVector *rxBurst; TransceiverState *state = &mStates[chan]; bool ctr_changed = false; + double rssi_offset; + static complex burst_shift_buffer[625]; + static signalVector shift_vec(burst_shift_buffer, 0, 625, dummy_alloc, dummy_free); + signalVector *shvec_ptr = &shift_vec; /* Blocking FIFO read */ radioVector *radio_burst = mReceiveFIFO[chan]->read(); @@ -650,7 +687,7 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) } /* Set time and determine correlation type */ - burstTime = radio_burst->getTime(); + burstTime = radio_burst->getTime() + cfg->ul_fn_offset; CorrType type = expectedCorrType(burstTime, chan); /* Initialize struct bi */ @@ -685,7 +722,7 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) /* Select the diversity channel with highest energy */ for (size_t i = 0; i < radio_burst->chans(); i++) { - float pow = energyDetect(*radio_burst->getVector(i), 20 * mSPSRx); + float pow = energyDetect(*radio_burst->getVector(i), 20 * cfg->rx_sps); if (pow > max) { max = pow; max_i = i; @@ -710,8 +747,9 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) state->mNoiseLev = state->mNoises.avg(); } - bi->rssi = 20.0 * log10(rxFullScale / avg) + rssiOffset; - bi->noise = 20.0 * log10(rxFullScale / state->mNoiseLev) + rssiOffset; + rssi_offset = rssiOffset(chan); + bi->rssi = 20.0 * log10(rxFullScale / avg) + rssi_offset; + bi->noise = 20.0 * log10(rxFullScale / state->mNoiseLev) + rssi_offset; if (type == IDLE) goto ret_idle; @@ -719,8 +757,15 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) max_toa = (type == RACH || type == EXT_RACH) ? mMaxExpectedDelayAB : mMaxExpectedDelayNB; + if (cfg->use_va) { + // shifted burst copy to make the old demod and detection happy + std::copy(burst->begin() + 20, burst->end() - 20, shift_vec.begin()); + } else { + shvec_ptr = burst; + } + /* Detect normal or RACH bursts */ - rc = detectAnyBurst(*burst, mTSC, BURST_THRESH, mSPSRx, type, max_toa, &ebp); + rc = detectAnyBurst(*shvec_ptr, mTSC, BURST_THRESH, cfg->rx_sps, type, max_toa, &ebp); if (rc <= 0) { if (rc == -SIGERR_CLIP) { LOGCHAN(chan, DTRXDUL, INFO) << "Clipping detected on received RACH or Normal Burst"; @@ -734,11 +779,16 @@ int Transceiver::pullRadioVector(size_t chan, struct trx_ul_burst_ind *bi) goto ret_idle; } - type = (CorrType) rc; + if (cfg->use_va) { + scaleVector(*burst, { (1. / (float)((1 << 14) - 1)), 0 }); + rxBurst = demodAnyBurst_va(*burst, (CorrType)rc, cfg->rx_sps, max_toa, mTSC); + } else { + rxBurst = demodAnyBurst(*shvec_ptr, (CorrType)rc, cfg->rx_sps, &ebp); + } + bi->toa = ebp.toa; bi->tsc = ebp.tsc; bi->ci = ebp.ci; - rxBurst = demodAnyBurst(*burst, mSPSRx, ebp.amp, ebp.toa, type); /* EDGE demodulator returns 444 (gSlotLen * 3) bits */ if (rxBurst->size() == EDGE_BURST_NBITS) { @@ -806,7 +856,7 @@ void Transceiver::ctrl_sock_send(ctrl_msg& m, int chan) struct osmo_fd *conn_bfd = &s.conn_bfd; s.txmsgqueue.push_back(m); - conn_bfd->when |= OSMO_FD_WRITE; + osmo_fd_write_enable(conn_bfd); } int Transceiver::ctrl_sock_write(int chan) @@ -821,7 +871,7 @@ int Transceiver::ctrl_sock_write(int chan) while (s.txmsgqueue.size()) { const ctrl_msg m = s.txmsgqueue.front(); - s.conn_bfd.when &= ~OSMO_FD_WRITE; + osmo_fd_write_disable(&s.conn_bfd); /* try to send it over the socket */ rc = write(s.conn_bfd.fd, m.data, strlen(m.data) + 1); @@ -829,7 +879,7 @@ int Transceiver::ctrl_sock_write(int chan) goto close; if (rc < 0) { if (errno == EAGAIN) { - s.conn_bfd.when |= OSMO_FD_WRITE; + osmo_fd_write_enable(&s.conn_bfd); break; } goto close; @@ -909,19 +959,18 @@ int Transceiver::ctrl_sock_handle_rx(int chan) sprintf(response, "RSP NOHANDOVER 0 %u %u", ts, ss); } } else if (match_cmd(command, "SETMAXDLY", ¶ms)) { - //set expected maximum time-of-arrival + //set expected maximum time-of-arrival for Access Bursts int maxDelay; sscanf(params, "%d", &maxDelay); mMaxExpectedDelayAB = maxDelay; // 1 GSM symbol is approx. 1 km sprintf(response,"RSP SETMAXDLY 0 %d",maxDelay); } else if (match_cmd(command, "SETMAXDLYNB", ¶ms)) { - //set expected maximum time-of-arrival + //set expected maximum time-of-arrival for Normal Bursts int maxDelay; sscanf(params, "%d", &maxDelay); mMaxExpectedDelayNB = maxDelay; // 1 GSM symbol is approx. 1 km sprintf(response,"RSP SETMAXDLYNB 0 %d",maxDelay); } else if (match_cmd(command, "SETRXGAIN", ¶ms)) { - //set expected maximum time-of-arrival int newGain; sscanf(params, "%d", &newGain); newGain = mRadioInterface->setRxGain(newGain, chan); @@ -955,7 +1004,7 @@ int Transceiver::ctrl_sock_handle_rx(int chan) // tune receiver int freqKhz; sscanf(params, "%d", &freqKhz); - mRxFreq = freqKhz * 1e3; + mRxFreq = (freqKhz + cfg->freq_offset_khz) * 1e3; if (!mRadioInterface->tuneRx(mRxFreq, chan)) { LOGCHAN(chan, DTRXCTRL, FATAL) << "RX failed to tune"; sprintf(response,"RSP RXTUNE 1 %d",freqKhz); @@ -966,7 +1015,7 @@ int Transceiver::ctrl_sock_handle_rx(int chan) // tune txmtr int freqKhz; sscanf(params, "%d", &freqKhz); - mTxFreq = freqKhz * 1e3; + mTxFreq = (freqKhz + cfg->freq_offset_khz) * 1e3; if (!mRadioInterface->tuneTx(mTxFreq, chan)) { LOGCHAN(chan, DTRXCTRL, FATAL) << "TX failed to tune"; sprintf(response,"RSP TXTUNE 1 %d",freqKhz); @@ -1004,7 +1053,7 @@ int Transceiver::ctrl_sock_handle_rx(int chan) LOGCHAN(chan, DTRXCTRL, INFO) << "BTS requests TRXD version switch: " << version_recv; if (version_recv > TRX_DATA_FORMAT_VER) { LOGCHAN(chan, DTRXCTRL, INFO) << "rejecting TRXD version " << version_recv - << "in favor of " << TRX_DATA_FORMAT_VER; + << " in favor of " << TRX_DATA_FORMAT_VER; sprintf(response, "RSP SETFORMAT %u %u", TRX_DATA_FORMAT_VER, version_recv); } else { LOGCHAN(chan, DTRXCTRL, NOTICE) << "switching to TRXD version " << version_recv; @@ -1055,8 +1104,8 @@ bool Transceiver::driveTxPriorityQueue(size_t chan) burstLen = gSlotLen; break; case sizeof(*dl) + EDGE_BURST_NBITS: /* EDGE burst */ - if (mSPSTx != 4) { - LOGCHAN(chan, DTRXDDL, ERROR) << "EDGE burst received but SPS is set to " << mSPSTx; + if (cfg->tx_sps != 4) { + LOGCHAN(chan, DTRXDDL, ERROR) << "EDGE burst received but SPS is set to " << cfg->tx_sps; return false; } burstLen = EDGE_BURST_NBITS; @@ -1163,11 +1212,13 @@ void Transceiver::logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi) else os << "-"; } + double rssi_offset = rssiOffset(chan); + LOGCHAN(chan, DTRXDUL, DEBUG) << std::fixed << std::right << " time: " << unsigned(bi->tn) << ":" << bi->fn - << " RSSI: " << std::setw(5) << std::setprecision(1) << (bi->rssi - rssiOffset) + << " RSSI: " << std::setw(5) << std::setprecision(1) << (bi->rssi - rssi_offset) << "dBFS/" << std::setw(6) << -bi->rssi << "dBm" - << " noise: " << std::setw(5) << std::setprecision(1) << (bi->noise - rssiOffset) + << " noise: " << std::setw(5) << std::setprecision(1) << (bi->noise - rssi_offset) << "dBFS/" << std::setw(6) << -bi->noise << "dBm" << " TOA: " << std::setw(5) << std::setprecision(2) << bi->toa << " C/I: " << std::setw(5) << std::setprecision(2) << bi->ci << "dB" diff --git a/Transceiver52M/Transceiver.h b/Transceiver52M/Transceiver.h index 7a05ac5..babe420 100644 --- a/Transceiver52M/Transceiver.h +++ b/Transceiver52M/Transceiver.h @@ -80,7 +80,7 @@ struct TransceiverState { /* Received noise energy levels */ float mNoiseLev; - noiseVector mNoises; + avgVector mNoises; /* Shadowed downlink attenuation */ int mPower; @@ -100,27 +100,19 @@ struct TransceiverState { class Transceiver { public: /** Transceiver constructor - @param wBasePort base port number of UDP sockets - @param TRXAddress IP address of the TRX, as a string - @param GSMcoreAddress IP address of the GSM core, as a string - @param wSPS number of samples per GSM symbol + @param cfg VTY populated config @param wTransmitLatency initial setting of transmit latency @param radioInterface associated radioInterface object */ - Transceiver(int wBasePort, - const char *TRXAddress, - const char *GSMcoreAddress, - size_t tx_sps, size_t rx_sps, size_t chans, + Transceiver(const struct trx_cfg *cfg, GSM::Time wTransmitLatency, - RadioInterface *wRadioInterface, - double wRssiOffset, int stackSize); + RadioInterface *wRadioInterface); /** Destructor */ ~Transceiver(); /** Start the control loop */ - bool init(FillerType filler, size_t rtsc, unsigned rach_delay, - bool edge, bool ext_rach); + bool init(void); /** attach the radioInterface receive FIFO */ bool receiveFIFO(VectorFIFO *wFIFO, size_t chan) @@ -133,7 +125,7 @@ public: } /** accessor for number of channels */ - size_t numChans() const { return mChans; }; + size_t numChans() const { return cfg->num_chans; }; /** Codes for channel combinations */ typedef enum { @@ -156,7 +148,7 @@ public: } ChannelCombination; private: - + size_t mChans; struct ctrl_msg { char data[101]; ctrl_msg() {}; @@ -170,17 +162,14 @@ struct ctrl_sock_state { } ~ctrl_sock_state() { if(conn_bfd.fd >= 0) { + osmo_fd_unregister(&conn_bfd); close(conn_bfd.fd); conn_bfd.fd = -1; - osmo_fd_unregister(&conn_bfd); } } }; - int mBasePort; - std::string mLocalAddr; - std::string mRemoteAddr; - + const struct trx_cfg *cfg; ///< VTY populated config std::vector<int> mDataSockets; ///< socket for writing to/reading from GSM core std::vector<ctrl_sock_state> mCtrlSockets; ///< socket for writing/reading control commands from GSM core int mClockSocket; ///< socket for writing clock updates to GSM core @@ -202,9 +191,6 @@ struct ctrl_sock_state { double txFullScale; ///< full scale input to radio double rxFullScale; ///< full scale output to radio - double rssiOffset; ///< RSSI to dBm conversion offset - int stackSize; ///< stack size for threads, 0 = OS default - /** modulate and add a burst to the transmit queue */ void addRadioVector(size_t chan, BitVector &bits, int RSSI, GSM::Time &wTime); @@ -233,12 +219,7 @@ struct ctrl_sock_state { /** drive handling of control messages from GSM core */ int ctrl_sock_handle_rx(int chan); - int mSPSTx; ///< number of samples per Tx symbol - int mSPSRx; ///< number of samples per Rx symbol - size_t mChans; - bool mExtRACH; - bool mEdge; bool mOn; ///< flag to indicate that transceiver is powered on bool mForceClockInterface; ///< flag to indicate whether IND CLOCK shall be sent unconditionally after transceiver is started bool mHandover[8][8]; ///< expect handover to the timeslot/subslot @@ -280,7 +261,7 @@ protected: friend void *RxLowerLoopAdapter(Transceiver *transceiver); friend void *TxLowerLoopAdapter(Transceiver *transceiver); - + double rssiOffset(size_t chan); void reset(); void logRxBurst(size_t chan, const struct trx_ul_burst_ind *bi); diff --git a/Transceiver52M/arch/arm/convert.c b/Transceiver52M/arch/arm/convert.c index ace1b6f..7145579 100644 --- a/Transceiver52M/arch/arm/convert.c +++ b/Transceiver52M/arch/arm/convert.c @@ -13,10 +13,6 @@ * 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 */ #include <malloc.h> diff --git a/Transceiver52M/arch/arm/convert_neon.S b/Transceiver52M/arch/arm/convert_neon.S index a1fbd40..6cf3c03 100644 --- a/Transceiver52M/arch/arm/convert_neon.S +++ b/Transceiver52M/arch/arm/convert_neon.S @@ -13,10 +13,6 @@ * 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 */ .syntax unified diff --git a/Transceiver52M/arch/arm/convolve.c b/Transceiver52M/arch/arm/convolve.c index adb718d..c2611b4 100644 --- a/Transceiver52M/arch/arm/convolve.c +++ b/Transceiver52M/arch/arm/convolve.c @@ -13,10 +13,6 @@ * 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 */ #include <malloc.h> diff --git a/Transceiver52M/arch/arm/convolve_neon.S b/Transceiver52M/arch/arm/convolve_neon.S index a3e1ba5..3209d60 100644 --- a/Transceiver52M/arch/arm/convolve_neon.S +++ b/Transceiver52M/arch/arm/convolve_neon.S @@ -13,10 +13,6 @@ * 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 diff --git a/Transceiver52M/arch/arm/mult.c b/Transceiver52M/arch/arm/mult.c index 9851626..251b3c9 100644 --- a/Transceiver52M/arch/arm/mult.c +++ b/Transceiver52M/arch/arm/mult.c @@ -13,10 +13,6 @@ * 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 */ #include <malloc.h> diff --git a/Transceiver52M/arch/arm/mult_neon.S b/Transceiver52M/arch/arm/mult_neon.S index 6318c50..b4cd229 100644 --- a/Transceiver52M/arch/arm/mult_neon.S +++ b/Transceiver52M/arch/arm/mult_neon.S @@ -13,10 +13,6 @@ * 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 */ .syntax unified diff --git a/Transceiver52M/arch/arm/scale.c b/Transceiver52M/arch/arm/scale.c index a3214f7..d44ffc8 100644 --- a/Transceiver52M/arch/arm/scale.c +++ b/Transceiver52M/arch/arm/scale.c @@ -13,10 +13,6 @@ * 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 */ #include <malloc.h> diff --git a/Transceiver52M/arch/arm/scale_neon.S b/Transceiver52M/arch/arm/scale_neon.S index f10de1e..c348f30 100644 --- a/Transceiver52M/arch/arm/scale_neon.S +++ b/Transceiver52M/arch/arm/scale_neon.S @@ -13,10 +13,6 @@ * 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 */ .syntax unified diff --git a/Transceiver52M/arch/common/convert_base.c b/Transceiver52M/arch/common/convert_base.c index 9876e83..9a01a1e 100644 --- a/Transceiver52M/arch/common/convert_base.c +++ b/Transceiver52M/arch/common/convert_base.c @@ -13,10 +13,6 @@ * 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 */ #include "convert.h" diff --git a/Transceiver52M/arch/common/convolve_base.c b/Transceiver52M/arch/common/convolve_base.c index bfda783..3765c5c 100644 --- a/Transceiver52M/arch/common/convolve_base.c +++ b/Transceiver52M/arch/common/convolve_base.c @@ -13,10 +13,6 @@ * 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 */ #include <malloc.h> diff --git a/Transceiver52M/arch/x86/convert.c b/Transceiver52M/arch/x86/convert.c index bbcfd67..596233c 100644 --- a/Transceiver52M/arch/x86/convert.c +++ b/Transceiver52M/arch/x86/convert.c @@ -11,10 +11,6 @@ * 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 */ #include <malloc.h> diff --git a/Transceiver52M/arch/x86/convert_sse_3.c b/Transceiver52M/arch/x86/convert_sse_3.c index 255db67..f00ecf5 100644 --- a/Transceiver52M/arch/x86/convert_sse_3.c +++ b/Transceiver52M/arch/x86/convert_sse_3.c @@ -11,10 +11,6 @@ * 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 */ #include <malloc.h> diff --git a/Transceiver52M/arch/x86/convert_sse_3.h b/Transceiver52M/arch/x86/convert_sse_3.h index c2f87d7..b5abe47 100644 --- a/Transceiver52M/arch/x86/convert_sse_3.h +++ b/Transceiver52M/arch/x86/convert_sse_3.h @@ -11,10 +11,6 @@ * 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 */ #pragma once diff --git a/Transceiver52M/arch/x86/convert_sse_4_1.c b/Transceiver52M/arch/x86/convert_sse_4_1.c index 42a235c..736a376 100644 --- a/Transceiver52M/arch/x86/convert_sse_4_1.c +++ b/Transceiver52M/arch/x86/convert_sse_4_1.c @@ -11,10 +11,6 @@ * 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 */ #include <malloc.h> diff --git a/Transceiver52M/arch/x86/convert_sse_4_1.h b/Transceiver52M/arch/x86/convert_sse_4_1.h index 57a5efb..63305e5 100644 --- a/Transceiver52M/arch/x86/convert_sse_4_1.h +++ b/Transceiver52M/arch/x86/convert_sse_4_1.h @@ -11,10 +11,6 @@ * 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 */ #pragma once diff --git a/Transceiver52M/arch/x86/convolve.c b/Transceiver52M/arch/x86/convolve.c index 81ea782..45a3719 100644 --- a/Transceiver52M/arch/x86/convolve.c +++ b/Transceiver52M/arch/x86/convolve.c @@ -11,10 +11,6 @@ * 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 */ #include <malloc.h> @@ -102,9 +98,9 @@ int convolve_real(const float *x, int x_len, #ifndef __OPTIMIZE__ if (bounds_check(x_len, h_len, y_len, start, len) < 0) return -1; - - memset(y, 0, len * 2 * sizeof(float)); #endif + memset(y, 0, len * 2 * sizeof(float)); + switch (h_len) { case 4: c.conv_real4(x, x_len, h, h_len, y, y_len, start, len); @@ -142,9 +138,9 @@ int convolve_complex(const float *x, int x_len, #ifndef __OPTIMIZE__ if (bounds_check(x_len, h_len, y_len, start, len) < 0) return -1; - - memset(y, 0, len * 2 * sizeof(float)); #endif + memset(y, 0, len * 2 * sizeof(float)); + if (!(h_len % 8)) c.conv_cmplx_8n(x, x_len, h, h_len, y, y_len, start, len); else if (!(h_len % 4)) diff --git a/Transceiver52M/arch/x86/convolve_sse_3.c b/Transceiver52M/arch/x86/convolve_sse_3.c index 8fd3b5e..ca4dc71 100644 --- a/Transceiver52M/arch/x86/convolve_sse_3.c +++ b/Transceiver52M/arch/x86/convolve_sse_3.c @@ -11,10 +11,6 @@ * 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 */ #include <malloc.h> diff --git a/Transceiver52M/arch/x86/convolve_sse_3.h b/Transceiver52M/arch/x86/convolve_sse_3.h index d929ef6..2cbc037 100644 --- a/Transceiver52M/arch/x86/convolve_sse_3.h +++ b/Transceiver52M/arch/x86/convolve_sse_3.h @@ -11,10 +11,6 @@ * 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 */ #pragma once diff --git a/Transceiver52M/device/Makefile.am b/Transceiver52M/device/Makefile.am index 93ba7e3..9af18f7 100644 --- a/Transceiver52M/device/Makefile.am +++ b/Transceiver52M/device/Makefile.am @@ -17,3 +17,7 @@ endif if DEVICE_LMS SUBDIRS += lms endif + +if DEVICE_BLADE +SUBDIRS += bladerf +endif diff --git a/Transceiver52M/device/bladerf/Makefile.am b/Transceiver52M/device/bladerf/Makefile.am new file mode 100644 index 0000000..e9917af --- /dev/null +++ b/Transceiver52M/device/bladerf/Makefile.am @@ -0,0 +1,11 @@ +include $(top_srcdir)/Makefile.common + +AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(BLADE_CFLAGS) + +noinst_HEADERS = bladerf.h + +noinst_LTLIBRARIES = libdevice.la + +libdevice_la_SOURCES = bladerf.cpp +libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la diff --git a/Transceiver52M/device/bladerf/bladerf.cpp b/Transceiver52M/device/bladerf/bladerf.cpp new file mode 100644 index 0000000..2f3c86a --- /dev/null +++ b/Transceiver52M/device/bladerf/bladerf.cpp @@ -0,0 +1,611 @@ +/* + * Copyright 2022 sysmocom - s.f.m.c. GmbH + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * See the COPYING file in the main directory for details. + */ + +#include <map> +#include <libbladeRF.h> +#include "radioDevice.h" +#include "bladerf.h" +#include "Threads.h" +#include "Logger.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +extern "C" { +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/vty/cpu_sched_vty.h> +} + +#define SAMPLE_BUF_SZ (1 << 20) + +#define B2XX_TIMING_4_4SPS 6.18462e-5 + +#define CHKRET() \ + { \ + if (status != 0) \ + LOGC(DDEV, ERROR) << bladerf_strerror(status); \ + } + +static const dev_map_t dev_param_map{ + { std::make_tuple(blade_dev_type::BLADE2, 4, 4), { 1, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B200 4 SPS" } }, +}; + +static const power_map_t dev_band_nom_power_param_map{ + { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_850), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_900), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1800), { 89.75, 7.5, -11.0 } }, + { std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1900), { 89.75, 7.7, -11.0 } }, +}; + +/* So far measurements done for B210 show really close to linear relationship + * between gain and real output power, so we simply adjust the measured offset + */ +static double TxGain2TxPower(const dev_band_desc &desc, double tx_gain_db) +{ + return desc.nom_out_tx_power - (desc.nom_uhd_tx_gain - tx_gain_db); +} +static double TxPower2TxGain(const dev_band_desc &desc, double tx_power_dbm) +{ + return desc.nom_uhd_tx_gain - (desc.nom_out_tx_power - tx_power_dbm); +} + +blade_device::blade_device(InterfaceType iface, const struct trx_cfg *cfg) + : RadioDevice(iface, cfg), band_manager(dev_band_nom_power_param_map, dev_param_map), dev(nullptr), + rx_gain_min(0.0), rx_gain_max(0.0), tx_spp(0), rx_spp(0), started(false), aligned(false), drop_cnt(0), + prev_ts(0), ts_initial(0), ts_offset(0), async_event_thrd(NULL) +{ +} + +blade_device::~blade_device() +{ + if (dev) { + bladerf_enable_module(dev, BLADERF_CHANNEL_RX(0), false); + bladerf_enable_module(dev, BLADERF_CHANNEL_TX(0), false); + } + + stop(); + + for (size_t i = 0; i < rx_buffers.size(); i++) + delete rx_buffers[i]; +} + +void blade_device::init_gains() +{ + double tx_gain_min, tx_gain_max; + int status; + + const struct bladerf_range *r; + bladerf_get_gain_range(dev, BLADERF_RX, &r); + + rx_gain_min = r->min; + rx_gain_max = r->max; + LOGC(DDEV, INFO) << "Supported Rx gain range [" << rx_gain_min << "; " << rx_gain_max << "]"; + + for (size_t i = 0; i < rx_gains.size(); i++) { + double gain = (rx_gain_min + rx_gain_max) / 2; + status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(i), BLADERF_GAIN_MGC); + CHKRET() + bladerf_gain_mode m; + bladerf_get_gain_mode(dev, BLADERF_CHANNEL_RX(i), &m); + LOGC(DDEV, INFO) << (m == BLADERF_GAIN_MANUAL ? "gain manual" : "gain AUTO"); + + status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0); + CHKRET() + int actual_gain; + status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain); + CHKRET() + LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale " + << r->scale << " actual " << actual_gain; + rx_gains[i] = actual_gain; + + status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0); + CHKRET() + status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain); + CHKRET() + LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale " + << r->scale << " actual " << actual_gain; + rx_gains[i] = actual_gain; + } + + status = bladerf_get_gain_range(dev, BLADERF_TX, &r); + CHKRET() + tx_gain_min = r->min; + tx_gain_max = r->max; + LOGC(DDEV, INFO) << "Supported Tx gain range [" << tx_gain_min << "; " << tx_gain_max << "]"; + + for (size_t i = 0; i < tx_gains.size(); i++) { + double gain = (tx_gain_min + tx_gain_max) / 2; + status = bladerf_set_gain(dev, BLADERF_CHANNEL_TX(i), 30); + CHKRET() + int actual_gain; + status = bladerf_get_gain(dev, BLADERF_CHANNEL_TX(i), &actual_gain); + CHKRET() + LOGC(DDEV, INFO) << "Default setting Tx gain for channel " << i << " to " << gain << " scale " + << r->scale << " actual " << actual_gain; + tx_gains[i] = actual_gain; + } + + return; +} + +void blade_device::set_rates() +{ + struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)), 6 }, actual; + auto status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_RX(0), &rate, &actual); + CHKRET() + status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_TX(0), &rate, &actual); + CHKRET() + + tx_rate = rx_rate = (double)rate.num / (double)rate.den; + + LOGC(DDEV, INFO) << "Rates set to" << tx_rate << " / " << rx_rate; + + bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL); + bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL); + + ts_offset = 60; // FIXME: actual blade offset, should equal b2xx +} + +double blade_device::setRxGain(double db, size_t chan) +{ + if (chan >= rx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + bladerf_set_gain(dev, BLADERF_CHANNEL_RX(chan), 30); //db); + int actual_gain; + bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain); + + rx_gains[chan] = actual_gain; + + LOGC(DDEV, INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)"; + + return rx_gains[chan]; +} + +double blade_device::getRxGain(size_t chan) +{ + if (chan >= rx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + return rx_gains[chan]; +} + +double blade_device::rssiOffset(size_t chan) +{ + double rssiOffset; + dev_band_desc desc; + + if (chan >= rx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + get_dev_band_desc(desc); + rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel; + return rssiOffset; +} + +double blade_device::setPowerAttenuation(int atten, size_t chan) +{ + double tx_power, db; + dev_band_desc desc; + + if (chan >= tx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel" << chan; + return 0.0f; + } + + get_dev_band_desc(desc); + tx_power = desc.nom_out_tx_power - atten; + db = TxPower2TxGain(desc, tx_power); + + bladerf_set_gain(dev, BLADERF_CHANNEL_TX(chan), 30); + int actual_gain; + bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain); + + tx_gains[chan] = actual_gain; + + LOGC(DDEV, INFO) + << "Set TX gain to " << tx_gains[chan] << "dB, ~" << TxGain2TxPower(desc, tx_gains[chan]) << " dBm " + << "(asked for " << db << " dB, ~" << tx_power << " dBm)"; + + return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]); +} +double blade_device::getPowerAttenuation(size_t chan) +{ + dev_band_desc desc; + if (chan >= tx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + get_dev_band_desc(desc); + return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]); +} + +int blade_device::getNominalTxPower(size_t chan) +{ + dev_band_desc desc; + get_dev_band_desc(desc); + + return desc.nom_out_tx_power; +} + +int blade_device::open() +{ + bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_VERBOSE); + bladerf_set_usb_reset_on_open(true); + auto success = bladerf_open(&dev, cfg->dev_args); + if (success != 0) { + struct bladerf_devinfo *info; + auto num_devs = bladerf_get_device_list(&info); + LOGC(DDEV, ALERT) << "No bladerf devices found with identifier '" << cfg->dev_args << "'"; + if (num_devs) { + for (int i = 0; i < num_devs; i++) + LOGC(DDEV, ALERT) << "Found device:" << info[i].product << " serial " << info[i].serial; + } + + return -1; + } + if (strcmp("bladerf2", bladerf_get_board_name(dev))) { + LOGC(DDEV, ALERT) << "Only BladeRF2 supported! found:" << bladerf_get_board_name(dev); + return -1; + } + + dev_type = blade_dev_type::BLADE2; + tx_window = TX_WINDOW_FIXED; + update_band_dev(dev_key(dev_type, tx_sps, rx_sps)); + + struct bladerf_devinfo info; + bladerf_get_devinfo(dev, &info); + LOGC(DDEV, INFO) << "Using discovered bladerf device " << info.serial; + + tx_freqs.resize(chans); + rx_freqs.resize(chans); + tx_gains.resize(chans); + rx_gains.resize(chans); + rx_buffers.resize(chans); + + switch (cfg->clock_ref) { + case REF_INTERNAL: + case REF_EXTERNAL: + break; + default: + LOGC(DDEV, ALERT) << "Invalid reference type"; + return -1; + } + + if (cfg->clock_ref == REF_EXTERNAL) { + bool is_locked; + int status = bladerf_set_pll_enable(dev, true); + CHKRET() + status = bladerf_set_pll_refclk(dev, 10000000); + CHKRET() + for (int i = 0; i < 20; i++) { + usleep(50 * 1000); + status = bladerf_get_pll_lock_state(dev, &is_locked); + CHKRET() + if (is_locked) + break; + } + if (!is_locked) { + LOGC(DDEV, ALERT) << "unable to lock refclk!"; + return -1; + } + } + + LOGC(DDEV, INFO) + << "Selected clock source is " << ((cfg->clock_ref == REF_INTERNAL) ? "internal" : "external 10Mhz"); + + set_rates(); + + /* + 1ts = 3/5200s + 1024*2 = small gap(~180us) every 9.23ms = every 16 ts? -> every 2 frames + 1024*1 = large gap(~627us) every 9.23ms = every 16 ts? -> every 2 frames + + rif convertbuffer = 625*4 = 2500 -> 4 ts + rif rxtxbuf = 4 * segment(625*4) = 10000 -> 16 ts + */ + const unsigned int num_buffers = 256; + const unsigned int buffer_size = 1024 * 4; /* Must be a multiple of 1024 */ + const unsigned int num_transfers = 32; + const unsigned int timeout_ms = 3500; + + bladerf_sync_config(dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers, + timeout_ms); + + bladerf_sync_config(dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers, + timeout_ms); + + /* Number of samples per over-the-wire packet */ + tx_spp = rx_spp = buffer_size; + + size_t buf_len = SAMPLE_BUF_SZ / sizeof(uint32_t); + for (size_t i = 0; i < rx_buffers.size(); i++) + rx_buffers[i] = new smpl_buf(buf_len); + + pkt_bufs = std::vector<std::vector<short> >(chans, std::vector<short>(2 * rx_spp)); + for (size_t i = 0; i < pkt_bufs.size(); i++) + pkt_ptrs.push_back(&pkt_bufs[i].front()); + + init_gains(); + + return NORMAL; +} + +bool blade_device::restart() +{ + /* Allow 100 ms delay to align multi-channel streams */ + double delay = 0.2; + int status; + + status = bladerf_enable_module(dev, BLADERF_CHANNEL_RX(0), true); + CHKRET() + status = bladerf_enable_module(dev, BLADERF_CHANNEL_TX(0), true); + CHKRET() + + bladerf_timestamp now; + status = bladerf_get_timestamp(dev, BLADERF_RX, &now); + ts_initial = now + rx_rate * delay; + LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl; + + return true; +} + +bool blade_device::start() +{ + LOGC(DDEV, INFO) << "Starting USRP..."; + + if (started) { + LOGC(DDEV, ERROR) << "Device already started"; + return false; + } + + if (!restart()) + return false; + + started = true; + return true; +} + +bool blade_device::stop() +{ + if (!started) + return false; + + /* reset internal buffer timestamps */ + for (size_t i = 0; i < rx_buffers.size(); i++) + rx_buffers[i]->reset(); + + band_reset(); + + started = false; + return true; +} + +int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun) +{ + ssize_t rc; + uint64_t ts; + + if (bufs.size() != chans) { + LOGC(DDEV, ALERT) << "Invalid channel combination " << bufs.size(); + return -1; + } + + *overrun = false; + *underrun = false; + + // Shift read time with respect to transmit clock + timestamp += ts_offset; + + ts = timestamp; + LOGC(DDEV, DEBUG) << "Requested timestamp = " << ts; + + // Check that timestamp is valid + rc = rx_buffers[0]->avail_smpls(timestamp); + if (rc < 0) { + LOGC(DDEV, ERROR) << rx_buffers[0]->str_code(rc); + LOGC(DDEV, ERROR) << rx_buffers[0]->str_status(timestamp); + return 0; + } + + struct bladerf_metadata meta = {}; + meta.timestamp = ts; + + while (rx_buffers[0]->avail_smpls(timestamp) < len) { + thread_enable_cancel(false); + int status = bladerf_sync_rx(dev, pkt_ptrs[0], len, &meta, 200U); + thread_enable_cancel(true); + + if (status != 0) + LOGC(DDEV, ERROR) << "RX broken: " << bladerf_strerror(status); + if (meta.flags & BLADERF_META_STATUS_OVERRUN) + LOGC(DDEV, ERROR) << "RX borken, OVERRUN: " << bladerf_strerror(status); + + size_t num_smpls = meta.actual_count; + ; + ts = meta.timestamp; + + for (size_t i = 0; i < rx_buffers.size(); i++) { + rc = rx_buffers[i]->write((short *)&pkt_bufs[i].front(), num_smpls, ts); + + // Continue on local overrun, exit on other errors + if ((rc < 0)) { + LOGC(DDEV, ERROR) << rx_buffers[i]->str_code(rc); + LOGC(DDEV, ERROR) << rx_buffers[i]->str_status(timestamp); + if (rc != smpl_buf::ERROR_OVERFLOW) + return 0; + } + } + meta = {}; + meta.timestamp = ts + num_smpls; + } + + for (size_t i = 0; i < rx_buffers.size(); i++) { + rc = rx_buffers[i]->read(bufs[i], len, timestamp); + if ((rc < 0) || (rc != len)) { + LOGC(DDEV, ERROR) << rx_buffers[i]->str_code(rc); + LOGC(DDEV, ERROR) << rx_buffers[i]->str_status(timestamp); + return 0; + } + } + + return len; +} + +int blade_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun, unsigned long long timestamp) +{ + *underrun = false; + static bool first_tx = true; + struct bladerf_metadata meta = {}; + if (first_tx) { + meta.timestamp = timestamp; + meta.flags = BLADERF_META_FLAG_TX_BURST_START; + first_tx = false; + } + + thread_enable_cancel(false); + int status = bladerf_sync_tx(dev, (const void *)bufs[0], len, &meta, 200U); + thread_enable_cancel(true); + + if (status != 0) + LOGC(DDEV, ERROR) << "TX broken: " << bladerf_strerror(status); + + return len; +} + +bool blade_device::updateAlignment(TIMESTAMP timestamp) +{ + return true; +} + +bool blade_device::set_freq(double freq, size_t chan, bool tx) +{ + if (tx) { + bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(chan), freq); + bladerf_frequency f; + bladerf_get_frequency(dev, BLADERF_CHANNEL_TX(chan), &f); + tx_freqs[chan] = f; + } else { + bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(chan), freq); + bladerf_frequency f; + bladerf_get_frequency(dev, BLADERF_CHANNEL_RX(chan), &f); + rx_freqs[chan] = f; + } + LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << (tx ? "TX" : "RX") << "): " << std::endl; + + return true; +} + +bool blade_device::setTxFreq(double wFreq, size_t chan) +{ + if (chan >= tx_freqs.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return false; + } + ScopedLock lock(tune_lock); + + if (!update_band_from_freq(wFreq, chan, true)) + return false; + + if (!set_freq(wFreq, chan, true)) + return false; + + return true; +} + +bool blade_device::setRxFreq(double wFreq, size_t chan) +{ + if (chan >= rx_freqs.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return false; + } + ScopedLock lock(tune_lock); + + if (!update_band_from_freq(wFreq, chan, false)) + return false; + + return set_freq(wFreq, chan, false); +} + +double blade_device::getTxFreq(size_t chan) +{ + if (chan >= tx_freqs.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0; + } + + return tx_freqs[chan]; +} + +double blade_device::getRxFreq(size_t chan) +{ + if (chan >= rx_freqs.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0; + } + + return rx_freqs[chan]; +} + +bool blade_device::requiresRadioAlign() +{ + return false; +} + +GSM::Time blade_device::minLatency() +{ + return GSM::Time(6, 7); +} + +TIMESTAMP blade_device::initialWriteTimestamp() +{ + return ts_initial; +} + +TIMESTAMP blade_device::initialReadTimestamp() +{ + return ts_initial; +} + +double blade_device::fullScaleInputValue() +{ + return (double)2047; +} + +double blade_device::fullScaleOutputValue() +{ + return (double)2047; +} + +RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) +{ + return new blade_device(type, cfg); +} diff --git a/Transceiver52M/device/bladerf/bladerf.h b/Transceiver52M/device/bladerf/bladerf.h new file mode 100644 index 0000000..e32581e --- /dev/null +++ b/Transceiver52M/device/bladerf/bladerf.h @@ -0,0 +1,195 @@ +/* + * Copyright 2022 sysmocom - s.f.m.c. GmbH + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * See the COPYING file in the main directory for details. + */ + +#pragma once + +#include <map> +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "bandmanager.h" +#include "radioDevice.h" +#include "smpl_buf.h" + +extern "C" { +#include <osmocom/gsm/gsm_utils.h> +} + +enum class blade_dev_type { BLADE1, BLADE2 }; + +struct dev_band_desc { + /* Maximum UHD Tx Gain which can be set/used without distorting the + output signal, and the resulting real output power measured when that + gain is used. Correct measured values only provided for B210 so far. */ + double nom_uhd_tx_gain; /* dB */ + double nom_out_tx_power; /* dBm */ + /* Factor used to infer base real RSSI offset on the Rx path based on current + configured RxGain. The resulting rssiOffset is added to the per burst + calculated energy in upper layers. These values were empirically + found and may change based on multiple factors, see OS#4468. + rssiOffset = rxGain + rxgain2rssioffset_rel; + */ + double rxgain2rssioffset_rel; /* dB */ +}; + +/* Device parameter descriptor */ +struct dev_desc { + unsigned channels; + double mcr; + double rate; + double offset; + std::string desc_str; +}; + +using dev_key = std::tuple<blade_dev_type, int, int>; +using dev_band_key = std::tuple<blade_dev_type, enum gsm_band>; +using power_map_t = std::map<dev_band_key, dev_band_desc>; +using dev_map_t = std::map<dev_key, dev_desc>; + +class blade_device : public RadioDevice, public band_manager<power_map_t, dev_map_t> { + public: + blade_device(InterfaceType iface, const struct trx_cfg *cfg); + ~blade_device(); + + int open(); + bool start(); + bool stop(); + bool restart(); + enum TxWindowType getWindowType() + { + return tx_window; + } + + int readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun); + + int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, TIMESTAMP timestamp); + + bool updateAlignment(TIMESTAMP timestamp); + + bool setTxFreq(double wFreq, size_t chan); + bool setRxFreq(double wFreq, size_t chan); + + TIMESTAMP initialWriteTimestamp(); + TIMESTAMP initialReadTimestamp(); + + double fullScaleInputValue(); + double fullScaleOutputValue(); + + double setRxGain(double db, size_t chan); + double getRxGain(size_t chan); + double maxRxGain(void) + { + return rx_gain_max; + } + double minRxGain(void) + { + return rx_gain_min; + } + double rssiOffset(size_t chan); + + double setPowerAttenuation(int atten, size_t chan); + double getPowerAttenuation(size_t chan = 0); + + int getNominalTxPower(size_t chan = 0); + + double getTxFreq(size_t chan); + double getRxFreq(size_t chan); + double getRxFreq(); + + bool setRxAntenna(const std::string &ant, size_t chan) + { + return {}; + }; + std::string getRxAntenna(size_t chan) + { + return {}; + }; + bool setTxAntenna(const std::string &ant, size_t chan) + { + return {}; + }; + std::string getTxAntenna(size_t chan) + { + return {}; + }; + + bool requiresRadioAlign(); + + GSM::Time minLatency(); + + inline double getSampleRate() + { + return tx_rate; + } + + /** Receive and process asynchronous message + @return true if message received or false on timeout or error + */ + bool recv_async_msg(); + + enum err_code { + ERROR_TIMING = -1, + ERROR_TIMEOUT = -2, + ERROR_UNRECOVERABLE = -3, + ERROR_UNHANDLED = -4, + }; + + protected: + struct bladerf *dev; + void *usrp_dev; + + enum TxWindowType tx_window; + enum blade_dev_type dev_type; + + double tx_rate, rx_rate; + + double rx_gain_min, rx_gain_max; + + std::vector<double> tx_gains, rx_gains; + std::vector<double> tx_freqs, rx_freqs; + size_t tx_spp, rx_spp; + + bool started; + bool aligned; + + size_t drop_cnt; + uint64_t prev_ts; + + TIMESTAMP ts_initial, ts_offset; + std::vector<smpl_buf *> rx_buffers; + /* Sample buffers used to receive samples: */ + std::vector<std::vector<short> > pkt_bufs; + /* Used to call UHD API: Buffer pointer of each elem in pkt_ptrs will + point to corresponding buffer of vector pkt_bufs. */ + std::vector<short *> pkt_ptrs; + + void init_gains(); + void set_channels(bool swap); + void set_rates(); + bool flush_recv(size_t num_pkts); + + bool set_freq(double freq, size_t chan, bool tx); + + Thread *async_event_thrd; + Mutex tune_lock; +}; diff --git a/Transceiver52M/device/common/Makefile.am b/Transceiver52M/device/common/Makefile.am index e14cc38..1a33592 100644 --- a/Transceiver52M/device/common/Makefile.am +++ b/Transceiver52M/device/common/Makefile.am @@ -1,10 +1,10 @@ include $(top_srcdir)/Makefile.common AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LMS_CFLAGS) +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) -noinst_HEADERS = radioDevice.h smpl_buf.h +noinst_HEADERS = radioDevice.h smpl_buf.h bandmanager.h noinst_LTLIBRARIES = libdevice_common.la diff --git a/Transceiver52M/device/common/bandmanager.h b/Transceiver52M/device/common/bandmanager.h new file mode 100644 index 0000000..d07f21d --- /dev/null +++ b/Transceiver52M/device/common/bandmanager.h @@ -0,0 +1,137 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <string> +#include <tuple> + +#include "Logger.h" + +extern "C" { +#include <osmocom/gsm/gsm_utils.h> +} + +template <typename powermapt, typename devmapt> +class band_manager { + using powerkeyt = typename powermapt::key_type; + using powermappedt = typename powermapt::mapped_type; + using devkeyt = typename devmapt::key_type; + devkeyt m_dev_type; + const powermapt &m_power_map; + const devmapt &m_dev_map; + powerkeyt m_fallback; + enum gsm_band m_band; + powermappedt m_band_desc; + bool band_ass_curr_sess{}; /* true if "band" was set after last POWEROFF */ + + // looks up either first tuple element (->enum) or straight enum + template <typename T, typename std::enable_if<std::is_enum<T>::value>::type *dummy = nullptr> + auto key_helper(T &t) -> T + { + return t; + } + + template <typename T> + auto key_helper(T t) -> typename std::tuple_element<0, T>::type + { + return std::get<0>(t); + } + + void assign_band_desc(enum gsm_band req_band) + { + auto key = key_helper(m_dev_type); + auto fallback_key = key_helper(m_fallback); + auto it = m_power_map.find({ key, req_band }); + if (it == m_power_map.end()) { + auto desc = m_dev_map.at(m_dev_type); + LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device " << desc.desc_str + << " on band " << gsm_band_name(req_band) << ", using fallback.."; + it = m_power_map.find({ fallback_key, req_band }); + } + OSMO_ASSERT(it != m_power_map.end()); + m_band_desc = it->second; + } + + bool set_band(enum gsm_band req_band) + { + if (band_ass_curr_sess && req_band != m_band) { + LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band) + << " different from previous band " << gsm_band_name(m_band); + return false; + } + + if (req_band != m_band) { + m_band = req_band; + assign_band_desc(m_band); + } + band_ass_curr_sess = true; + return true; + } + + public: + band_manager(const devkeyt &dev_type, const powermapt &power_map, const devmapt &dev_map, powerkeyt fallback) + : m_dev_type(dev_type), m_power_map(power_map), m_dev_map(dev_map), m_fallback(fallback), + m_band((enum gsm_band)0) + { + } + band_manager(const powermapt &power_map, const devmapt &dev_map) + : m_dev_type(dev_map.begin()->first), m_power_map(power_map), m_dev_map(dev_map), + m_fallback(m_power_map.begin()->first), m_band((enum gsm_band)0) + { + } + void band_reset() + { + band_ass_curr_sess = false; + } + + void update_band_dev(devkeyt dev_type) { + m_dev_type = dev_type; + } + + void get_dev_band_desc(powermappedt &desc) + { + if (m_band == 0) { + LOGC(DDEV, ERROR) + << "Power parameters requested before Tx Frequency was set! Providing band 900 by default..."; + assign_band_desc(GSM_BAND_900); + } + desc = m_band_desc; + } + + bool update_band_from_freq(double wFreq, int chan, bool is_tx) + { + enum gsm_band req_band; + auto dirstr = is_tx ? "Tx" : "Rx"; + auto req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, !is_tx); + if (req_arfcn == 0xffff) { + LOGCHAN(chan, DDEV, ALERT) + << "Unknown ARFCN for " << dirstr << " Frequency " << wFreq / 1000 << " kHz"; + return false; + } + if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) { + LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for " << dirstr << " Frequency " << wFreq + << " Hz (ARFCN " << req_arfcn << " )"; + return false; + } + + return set_band(req_band); + } +};
\ No newline at end of file diff --git a/Transceiver52M/device/common/radioDevice.h b/Transceiver52M/device/common/radioDevice.h index 45fcdad..5c962d1 100644 --- a/Transceiver52M/device/common/radioDevice.h +++ b/Transceiver52M/device/common/radioDevice.h @@ -51,13 +51,10 @@ class RadioDevice { MULTI_ARFCN, }; - static RadioDevice *make(size_t tx_sps, size_t rx_sps, InterfaceType type, - size_t chans = 1, double offset = 0.0, - const std::vector<std::string>& tx_paths = std::vector<std::string>(1, ""), - const std::vector<std::string>& rx_paths = std::vector<std::string>(1, "")); + static RadioDevice *make(InterfaceType type, const struct trx_cfg *cfg); /** Initialize the USRP */ - virtual int open(const std::string &args, int ref, bool swap_channels)=0; + virtual int open() = 0; virtual ~RadioDevice() { } @@ -125,6 +122,9 @@ class RadioDevice { /** return minimum Rx Gain **/ virtual double minRxGain(void) = 0; + /** return base RSSI offset to apply for received samples **/ + virtual double rssiOffset(size_t chan) = 0; + /** returns the Nominal transmit output power of the transceiver in dBm, negative on error **/ virtual int getNominalTxPower(size_t chan = 0) = 0; @@ -161,24 +161,30 @@ class RadioDevice { double lo_offset; std::vector<std::string> tx_paths, rx_paths; std::vector<struct device_counters> m_ctr; - - RadioDevice(size_t tx_sps, size_t rx_sps, InterfaceType type, size_t chan_num, double offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths): - tx_sps(tx_sps), rx_sps(rx_sps), iface(type), chans(chan_num), lo_offset(offset), - tx_paths(tx_paths), rx_paths(rx_paths) - { - if (iface == MULTI_ARFCN) { - LOGC(DDEV, INFO) << "Multi-ARFCN: "<< chan_num << " logical chans -> 1 physical chans"; - chans = 1; - } - - m_ctr.resize(chans); - for (size_t i = 0; i < chans; i++) { - memset(&m_ctr[i], 0, sizeof(m_ctr[i])); - m_ctr[i].chan = i; - } - } + const struct trx_cfg *cfg; + +#define charp2str(a) ((a) ? std::string(a) : std::string("")) + + RadioDevice(InterfaceType type, const struct trx_cfg *cfg) + : tx_sps(cfg->tx_sps), rx_sps(cfg->rx_sps), iface(type), chans(cfg->num_chans), lo_offset(cfg->offset), + m_ctr(chans), cfg(cfg) + { + /* Generate vector of rx/tx_path: */ + for (unsigned int i = 0; i < cfg->num_chans; i++) { + rx_paths.push_back(charp2str(cfg->chans[i].rx_path)); + tx_paths.push_back(charp2str(cfg->chans[i].tx_path)); + } + + if (iface == MULTI_ARFCN) { + LOGC(DDEV, INFO) << "Multi-ARFCN: " << chans << " logical chans -> 1 physical chans"; + chans = 1; + } + + for (size_t i = 0; i < chans; i++) { + memset(&m_ctr[i], 0, sizeof(m_ctr[i])); + m_ctr[i].chan = i; + } + } bool set_antennas() { unsigned int i; diff --git a/Transceiver52M/device/ipc/IPCDevice.cpp b/Transceiver52M/device/ipc/IPCDevice.cpp index 32a46e5..1d2b89a 100644 --- a/Transceiver52M/device/ipc/IPCDevice.cpp +++ b/Transceiver52M/device/ipc/IPCDevice.cpp @@ -56,20 +56,13 @@ using namespace std; static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags); -IPCDevice::IPCDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset, - const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths) - : RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths), tx_attenuation(), - tmp_state(IPC_IF_MSG_GREETING_REQ), shm(NULL), shm_dec(0), started(false) +IPCDevice::IPCDevice(InterfaceType iface, const struct trx_cfg *cfg) + : RadioDevice(iface, cfg), sk_chan_state(chans, ipc_per_trx_sock_state()), tx_attenuation(), + tmp_state(IPC_IF_MSG_GREETING_REQ), shm(NULL), shm_dec(0), rx_buffers(chans), started(false), tx_gains(chans), + rx_gains(chans) { LOGC(DDEV, INFO) << "creating IPC device..."; - rx_gains.resize(chans); - tx_gains.resize(chans); - - rx_buffers.resize(chans); - - sk_chan_state.resize(chans, ipc_per_trx_sock_state()); - /* Set up per-channel Rx timestamp based Ring buffers */ for (size_t i = 0; i < rx_buffers.size(); i++) rx_buffers[i] = new smpl_buf(SAMPLE_BUF_SZ / sizeof(uint32_t)); @@ -100,12 +93,14 @@ IPCDevice::~IPCDevice() int IPCDevice::ipc_shm_connect(const char *shm_name) { int fd; + char err_buf[256]; size_t shm_len; int rc; LOGP(DDEV, LOGL_NOTICE, "Opening shm path %s\n", shm_name); if ((fd = shm_open(shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) < 0) { - LOGP(DDEV, LOGL_ERROR, "shm_open %d: %s\n", errno, strerror(errno)); + LOGP(DDEV, LOGL_ERROR, "shm_open %d: %s\n", errno, + strerror_r(errno, err_buf, sizeof(err_buf))); rc = -errno; goto err_shm_open; } @@ -113,7 +108,8 @@ int IPCDevice::ipc_shm_connect(const char *shm_name) // Get size of the allocated memory struct stat shm_stat; if (fstat(fd, &shm_stat) < 0) { - LOGP(DDEV, LOGL_ERROR, "fstat %d: %s\n", errno, strerror(errno)); + LOGP(DDEV, LOGL_ERROR, "fstat %d: %s\n", errno, + strerror_r(errno, err_buf, sizeof(err_buf))); rc = -errno; goto err_mmap; } @@ -122,7 +118,8 @@ int IPCDevice::ipc_shm_connect(const char *shm_name) LOGP(DDEV, LOGL_NOTICE, "mmaping shared memory fd %d (size=%zu)\n", fd, shm_len); if ((shm = mmap(NULL, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { - LOGP(DDEV, LOGL_ERROR, "mmap %d: %s\n", errno, strerror(errno)); + LOGP(DDEV, LOGL_ERROR, "mmap %d: %s\n", errno, + strerror_r(errno, err_buf, sizeof(err_buf))); rc = -errno; goto err_mmap; } @@ -522,7 +519,7 @@ static int ipc_sock_send(struct ipc_per_trx_sock_state *state, struct msgb *msg) return -EIO; } msgb_enqueue(&state->upqueue, msg); - conn_bfd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(conn_bfd); return 0; } @@ -539,9 +536,9 @@ void IPCDevice::ipc_sock_close(struct ipc_per_trx_sock_state *state) LOGP(DDEV, LOGL_NOTICE, "IPC socket has LOST connection\n"); + osmo_fd_unregister(bfd); close(bfd->fd); bfd->fd = -1; - osmo_fd_unregister(bfd); /* flush the queue */ while (!llist_empty(&state->upqueue)) { @@ -659,7 +656,7 @@ int IPCDevice::ipc_sock_write(struct osmo_fd *bfd) msg = llist_entry(master_sk_state.upqueue.next, struct msgb, list); ipc_prim = (struct ipc_sk_if *)msg->data; - bfd->when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(bfd); /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ if (!msgb_length(msg)) { @@ -676,7 +673,7 @@ int IPCDevice::ipc_sock_write(struct osmo_fd *bfd) goto close; if (rc < 0) { if (errno == EAGAIN) { - bfd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(bfd); break; } goto close; @@ -706,7 +703,7 @@ int IPCDevice::ipc_chan_sock_write(struct osmo_fd *bfd) /* peek at the beginning of the queue */ msg = llist_entry(sk_chan_state[bfd->priv_nr].upqueue.next, struct msgb, list); ipc_prim = (struct ipc_sk_chan_if *)msg->data; - bfd->when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(bfd); /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ if (!msgb_length(msg)) { LOGP(DDEV, LOGL_ERROR, @@ -722,7 +719,7 @@ int IPCDevice::ipc_chan_sock_write(struct osmo_fd *bfd) goto close; if (rc < 0) { if (errno == EAGAIN) { - bfd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(bfd); break; } goto close; @@ -746,12 +743,12 @@ static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags) IPCDevice *device = static_cast<IPCDevice *>(bfd->data); int rc = 0; - if (flags & BSC_FD_READ) + if (flags & OSMO_FD_READ) rc = device->ipc_sock_read(bfd); if (rc < 0) return rc; - if (flags & BSC_FD_WRITE) + if (flags & OSMO_FD_WRITE) rc = device->ipc_sock_write(bfd); return rc; @@ -762,22 +759,23 @@ static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags) IPCDevice *device = static_cast<IPCDevice *>(bfd->data); int rc = 0; - if (flags & BSC_FD_READ) + if (flags & OSMO_FD_READ) rc = device->ipc_chan_sock_read(bfd); if (rc < 0) return rc; - if (flags & BSC_FD_WRITE) + if (flags & OSMO_FD_WRITE) rc = device->ipc_chan_sock_write(bfd); return rc; } -int IPCDevice::open(const std::string &args, int ref, bool swap_channels) +int IPCDevice::open() { std::string k, v; std::string::size_type keyend; int rc; + std::string args(cfg->dev_args); if ((keyend = args.find('=')) != std::string::npos) { k = args.substr(0, keyend++); @@ -793,7 +791,7 @@ int IPCDevice::open(const std::string &args, int ref, bool swap_channels) INIT_LLIST_HEAD(&master_sk_state.upqueue); rc = osmo_sock_unix_init_ofd(&master_sk_state.conn_bfd, SOCK_SEQPACKET, 0, v.c_str(), OSMO_SOCK_F_CONNECT); if (rc < 0) { - LOGC(DDEV, ERROR) << "Failed to connect to the BTS (" << v << "). " + LOGC(DDEV, ERROR) << "Failed to connect to the IPC device (" << v << "). " << "Retrying...\n"; osmo_timer_setup(&master_sk_state.timer, ipc_sock_timeout, NULL); osmo_timer_schedule(&master_sk_state.timer, 5, 0); @@ -812,7 +810,7 @@ int IPCDevice::open(const std::string &args, int ref, bool swap_channels) while (tmp_state != IPC_IF_MSG_INFO_CNF) osmo_select_main(0); - ipc_tx_open_req(&master_sk_state, chans, ref); + ipc_tx_open_req(&master_sk_state, chans, cfg->clock_ref); /* Wait until confirmation is recieved */ while (tmp_state != IPC_IF_MSG_OPEN_CNF) osmo_select_main(0); @@ -835,6 +833,7 @@ void IPCDevice::manually_poll_sock_fds() { struct timeval wait = { 0, 100000 }; fd_set crfds, cwfds; + char err_buf[256]; int max_fd = 0; FD_ZERO(&crfds); @@ -849,7 +848,11 @@ void IPCDevice::manually_poll_sock_fds() FD_SET(curr_fd->fd, &cwfds); } - select(max_fd + 1, &crfds, &cwfds, 0, &wait); + if (select(max_fd + 1, &crfds, &cwfds, 0, &wait) < 0) { + LOGP(DDEV, LOGL_ERROR, "select() failed: %s\n", + strerror_r(errno, err_buf, sizeof(err_buf))); + return; + } for (unsigned int i = 0; i < chans; i++) { int flags = 0; @@ -1256,16 +1259,15 @@ bool IPCDevice::setRxFreq(double wFreq, size_t chan) return send_chan_wait_rsp(chan, msg, IPC_IF_MSG_SETFREQ_CNF); } -RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset, - const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths) +RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) { - if (tx_sps != rx_sps) { + if (cfg->tx_sps != cfg->rx_sps) { LOGC(DDEV, ERROR) << "IPC Requires tx_sps == rx_sps"; return NULL; } - if (lo_offset != 0.0) { + if (cfg->offset != 0.0) { LOGC(DDEV, ERROR) << "IPC doesn't support lo_offset"; return NULL; } - return new IPCDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); + return new IPCDevice(type, cfg); } diff --git a/Transceiver52M/device/ipc/IPCDevice.h b/Transceiver52M/device/ipc/IPCDevice.h index a7618f9..0cf3d46 100644 --- a/Transceiver52M/device/ipc/IPCDevice.h +++ b/Transceiver52M/device/ipc/IPCDevice.h @@ -115,12 +115,11 @@ class IPCDevice : public RadioDevice { int ipc_chan_sock_write(osmo_fd *bfd); /** Object constructor */ - IPCDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset, - const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths); + IPCDevice(InterfaceType iface, const struct trx_cfg *cfg); virtual ~IPCDevice() override; /** Instantiate the IPC */ - virtual int open(const std::string &args, int ref, bool swap_channels) override; + virtual int open() override; /** Start the IPC */ virtual bool start() override; @@ -198,6 +197,9 @@ class IPCDevice : public RadioDevice { /** return minimum Rx Gain **/ virtual double minRxGain(void) override; + /* FIXME: return rx_gains[chan] ? receive factor from IPC Driver? */ + double rssiOffset(size_t chan) override { return 0.0f; }; + double setPowerAttenuation(int atten, size_t chan) override; double getPowerAttenuation(size_t chan = 0) override; diff --git a/Transceiver52M/device/ipc/Makefile.am b/Transceiver52M/device/ipc/Makefile.am index e4dc51a..c08621f 100644 --- a/Transceiver52M/device/ipc/Makefile.am +++ b/Transceiver52M/device/ipc/Makefile.am @@ -1,9 +1,8 @@ include $(top_srcdir)/Makefile.common -AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common -AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -AM_LDFLAGS = -lpthread -lrt +AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(UHD_CFLAGS) +AM_CXXFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(UHD_CFLAGS) noinst_HEADERS = IPCDevice.h shm.h ipc_shm.h ipc_chan.h ipc_sock.h @@ -14,29 +13,22 @@ endif noinst_LTLIBRARIES = libdevice.la libdevice_la_SOURCES = IPCDevice.cpp shm.c ipc_shm.c ipc_chan.c ipc_sock.c -libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la -libdevice_la_CXXFLAGS = $(AM_CXXFLAGS) -DIPCMAGIC +libdevice_la_CPPFLAGS = $(AM_CPPFLAGS) -DIPCMAGIC +libdevice_la_LIBADD = \ + $(top_builddir)/Transceiver52M/device/common/libdevice_common.la \ + -lpthread \ + -lrt \ + $(NULL) if DEVICE_UHD -#work around distclean issue on older autotools vers: -#a direct build of ../uhd/UHDDevice.cpp tries to clean -#../uhd/.dep/UHDDevice.Plo twice and fails -uhddev_ipc.cpp: - echo "#include \"../uhd/UHDDevice.cpp\"" >$@ -CLEANFILES= uhddev_ipc.cpp - bin_PROGRAMS = ipc-driver-test #ipc_driver_test_SHORTNAME = drvt -ipc_driver_test_SOURCES = ipc-driver-test.c uhdwrap.cpp ipc_shm.c ipc_chan.c ipc_sock.c uhddev_ipc.cpp +ipc_driver_test_SOURCES = ipc-driver-test.c uhdwrap.cpp ../uhd/UHDDevice.cpp ipc_driver_test_LDADD = \ - shm.lo \ + libdevice.la \ + $(COMMON_LA) \ $(LIBOSMOCORE_LIBS) \ - $(LIBOSMOCTRL_LIBS) \ - $(LIBOSMOVTY_LIBS) -ipc_driver_test_CXXFLAGS = $(AM_CXXFLAGS) $(UHD_CFLAGS) -ipc_driver_test_CPPFLAGS = $(AM_CPPFLAGS) $(UHD_CFLAGS) -ipc_driver_test_CFLAGS = $(AM_CFLAGS) $(UHD_CFLAGS) -ipc_driver_test_LDFLAGS = $(AM_LDFLAGS) $(UHD_LIBS) -ipc_driver_test_LDADD += $(top_builddir)/Transceiver52M/device/common/libdevice_common.la $(top_builddir)/CommonLibs/libcommon.la + $(UHD_LIBS) \ + $(NULL) endif diff --git a/Transceiver52M/device/ipc/ipc-driver-test.c b/Transceiver52M/device/ipc/ipc-driver-test.c index 44c8b0e..d8284c7 100644 --- a/Transceiver52M/device/ipc/ipc-driver-test.c +++ b/Transceiver52M/device/ipc/ipc-driver-test.c @@ -49,6 +49,7 @@ #include "ipc_sock.h" #define DEFAULT_SHM_NAME "/osmo-trx-ipc-driver-shm2" +#define IPC_SOCK_PATH_PREFIX "/tmp" static void *tall_ctx; struct ipc_sock_state *global_ipc_sock_state; @@ -63,6 +64,11 @@ void *global_dev; static struct ipc_shm_region *decoded_region; +static struct { + int msocknum; + char *ud_prefix_dir; +} cmdline_cfg; + static const struct log_info_cat default_categories[] = { [DMAIN] = { .name = "DMAIN", @@ -185,8 +191,8 @@ static int ipc_tx_open_cnf(int rc, uint32_t num_chans, int32_t timingoffset) chan_info = ipc_prim->u.open_cnf.chan_info; for (i = 0; i < num_chans; i++) { - snprintf(chan_info->chan_ipc_sk_path, sizeof(chan_info->chan_ipc_sk_path), "%s_%d", - IPC_SOCK_PATH_PREFIX, i); + snprintf(chan_info->chan_ipc_sk_path, sizeof(chan_info->chan_ipc_sk_path),"%s/ipc_sock%d_%d", + cmdline_cfg.ud_prefix_dir, cmdline_cfg.msocknum, i); /* FIXME: dynamc chan limit, currently 8 */ if (i < 8) ipc_sock_init(chan_info->chan_ipc_sk_path, &global_ctrl_socks[i], ipc_chan_sock_accept, i); @@ -394,10 +400,7 @@ int ipc_sock_init(const char *path, struct ipc_sock_state **global_state_var, return -1; } - bfd->when = BSC_FD_READ; - bfd->cb = sock_callback_fn; - bfd->data = state; - bfd->priv_nr = n; + osmo_fd_setup(bfd, bfd->fd, OSMO_FD_READ, sock_callback_fn, state, n); rc = osmo_fd_register(bfd); if (rc < 0) { @@ -416,20 +419,20 @@ static void print_help(void) { printf("ipc-driver-test Usage:\n" " -h --help This message\n" + " -u --unix-sk-dir DIR Existing directory where to create the Master socket\n" " -n --sock-num NR Master socket suffix number NR\n"); } -static int msocknum = 0; - static void handle_options(int argc, char **argv) { while (1) { int option_index = 0, c; const struct option long_options[] = { { "help", 0, 0, 'h' }, + { "unix-sk-dir", 1, 0, 'u' }, { "sock-num", 1, 0, 'n' }, { 0, 0, 0, 0 } }; - c = getopt_long(argc, argv, "hn:", long_options, &option_index); + c = getopt_long(argc, argv, "hu:n:", long_options, &option_index); if (c == -1) break; @@ -438,9 +441,13 @@ static void handle_options(int argc, char **argv) print_help(); exit(0); break; + case 'u': + cmdline_cfg.ud_prefix_dir = talloc_strdup(tall_ctx, optarg); + break; case 'n': - msocknum = atoi(optarg); + cmdline_cfg.msocknum = atoi(optarg); break; + default: exit(2); break; @@ -455,14 +462,19 @@ static void handle_options(int argc, char **argv) int main(int argc, char **argv) { - char ipc_msock_path[sizeof(IPC_SOCK_PATH_PREFIX) + 3]; + char ipc_msock_path[128]; tall_ctx = talloc_named_const(NULL, 0, "OsmoTRX"); msgb_talloc_ctx_init(tall_ctx, 0); osmo_init_logging2(tall_ctx, &log_infox); log_enable_multithread(); handle_options(argc, argv); - snprintf(ipc_msock_path, sizeof(ipc_msock_path), "%s%d", IPC_SOCK_PATH_PREFIX, msocknum); + + if (!cmdline_cfg.ud_prefix_dir) + cmdline_cfg.ud_prefix_dir = talloc_strdup(tall_ctx, IPC_SOCK_PATH_PREFIX); + + + snprintf(ipc_msock_path, sizeof(ipc_msock_path), "%s/ipc_sock%d", cmdline_cfg.ud_prefix_dir, cmdline_cfg.msocknum); LOGP(DMAIN, LOGL_INFO, "Starting %s\n", argv[0]); ipc_sock_init(ipc_msock_path, &global_ipc_sock_state, ipc_sock_accept, 0); diff --git a/Transceiver52M/device/ipc/ipc_chan.c b/Transceiver52M/device/ipc/ipc_chan.c index 9e35fb2..2a6f490 100644 --- a/Transceiver52M/device/ipc/ipc_chan.c +++ b/Transceiver52M/device/ipc/ipc_chan.c @@ -142,7 +142,7 @@ int ipc_chan_sock_send(struct msgb *msg, uint8_t chan_nr) return -EIO; } msgb_enqueue(&state->upqueue, msg); - conn_bfd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(conn_bfd); return 0; } @@ -160,7 +160,7 @@ static int ipc_chan_sock_write(struct osmo_fd *bfd) msg = llist_entry(state->upqueue.next, struct msgb, list); ipc_prim = (struct ipc_sk_chan_if *)msg->data; - bfd->when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(bfd); /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ if (!msgb_length(msg)) { @@ -177,7 +177,7 @@ static int ipc_chan_sock_write(struct osmo_fd *bfd) goto close; if (rc < 0) { if (errno == EAGAIN) { - bfd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(bfd); break; } goto close; @@ -200,12 +200,12 @@ static int ipc_chan_sock_cb(struct osmo_fd *bfd, unsigned int flags) { int rc = 0; - if (flags & BSC_FD_READ) + if (flags & OSMO_FD_READ) rc = ipc_chan_sock_read(bfd); if (rc < 0) return rc; - if (flags & BSC_FD_WRITE) + if (flags & OSMO_FD_WRITE) rc = ipc_chan_sock_write(bfd); return rc; @@ -231,18 +231,13 @@ int ipc_chan_sock_accept(struct osmo_fd *bfd, unsigned int flags) "osmo-trx connects but we already have " "another active connection ?!?\n"); /* We already have one IPC connected, this is all we support */ - state->listen_bfd.when &= ~BSC_FD_READ; + osmo_fd_read_disable(&state->listen_bfd); close(rc); return 0; } - conn_bfd->fd = rc; - conn_bfd->when = BSC_FD_READ; - conn_bfd->cb = ipc_chan_sock_cb; - conn_bfd->data = state; - /* copy chan nr, required for proper bfd<->chan # mapping */ - conn_bfd->priv_nr = bfd->priv_nr; + osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, ipc_chan_sock_cb, state, bfd->priv_nr); if (osmo_fd_register(conn_bfd) != 0) { LOGP(DDEV, LOGL_ERROR, diff --git a/Transceiver52M/device/ipc/ipc_sock.c b/Transceiver52M/device/ipc/ipc_sock.c index 435dc3c..9e8ab82 100644 --- a/Transceiver52M/device/ipc/ipc_sock.c +++ b/Transceiver52M/device/ipc/ipc_sock.c @@ -84,7 +84,7 @@ int ipc_sock_send(struct msgb *msg) return -EIO; } msgb_enqueue(&state->upqueue, msg); - conn_bfd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(conn_bfd); return 0; } @@ -97,12 +97,12 @@ void ipc_sock_close(struct ipc_sock_state *state) ipc_exit_requested = 1; + osmo_fd_unregister(bfd); close(bfd->fd); bfd->fd = -1; - osmo_fd_unregister(bfd); /* re-enable the generation of ACCEPT for new connections */ - state->listen_bfd.when |= BSC_FD_READ; + osmo_fd_read_enable(&state->listen_bfd); /* flush the queue */ while (!llist_empty(&state->upqueue)) { @@ -172,7 +172,7 @@ static int ipc_sock_write(struct osmo_fd *bfd) msg = llist_entry(state->upqueue.next, struct msgb, list); ipc_prim = (struct ipc_sk_if *)msg->data; - bfd->when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(bfd); /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ if (!msgb_length(msg)) { @@ -189,7 +189,7 @@ static int ipc_sock_write(struct osmo_fd *bfd) goto close; if (rc < 0) { if (errno == EAGAIN) { - bfd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(bfd); break; } goto close; @@ -212,12 +212,12 @@ static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags) { int rc = 0; - if (flags & BSC_FD_READ) + if (flags & OSMO_FD_READ) rc = ipc_sock_read(bfd); if (rc < 0) return rc; - if (flags & BSC_FD_WRITE) + if (flags & OSMO_FD_WRITE) rc = ipc_sock_write(bfd); return rc; @@ -244,15 +244,12 @@ int ipc_sock_accept(struct osmo_fd *bfd, unsigned int flags) "ip clent connects but we already have " "another active connection ?!?\n"); /* We already have one IPC connected, this is all we support */ - state->listen_bfd.when &= ~BSC_FD_READ; + osmo_fd_read_disable(&state->listen_bfd); close(rc); return 0; } - conn_bfd->fd = rc; - conn_bfd->when = BSC_FD_READ; - conn_bfd->cb = ipc_sock_cb; - conn_bfd->data = state; + osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, ipc_sock_cb, state, 0); if (osmo_fd_register(conn_bfd) != 0) { LOGP(DDEV, LOGL_ERROR, diff --git a/Transceiver52M/device/ipc/shm.h b/Transceiver52M/device/ipc/shm.h index fcddd54..46c3add 100644 --- a/Transceiver52M/device/ipc/shm.h +++ b/Transceiver52M/device/ipc/shm.h @@ -79,8 +79,6 @@ struct ipc_shm_region *ipc_shm_decode_region(void *tall_ctx, struct ipc_shm_raw_ ////////////////// // Master socket ////////////////// - -#define IPC_SOCK_PATH_PREFIX "/tmp/ipc_sock" #define IPC_SOCK_API_VERSION 1 /* msg_type */ diff --git a/Transceiver52M/device/ipc/uhdwrap.cpp b/Transceiver52M/device/ipc/uhdwrap.cpp index 130f80a..302f763 100644 --- a/Transceiver52M/device/ipc/uhdwrap.cpp +++ b/Transceiver52M/device/ipc/uhdwrap.cpp @@ -31,14 +31,16 @@ extern "C" { #include "../uhd/UHDDevice.h" #include "uhdwrap.h" -#include "trx_vty.h" #include "Logger.h" #include "Threads.h" #include "Utils.h" -int uhd_wrap::open(const std::string &args, int ref, bool swap_channels) +// no vty source for cfg params here, so we have to build our own +static struct trx_cfg actual_cfg = {}; + +int uhd_wrap::open() { - int rv = uhd_device::open(args, ref, swap_channels); + int rv = uhd_device::open(); samps_per_buff_rx = rx_stream->get_max_num_samps(); samps_per_buff_tx = tx_stream->get_max_num_samps(); channel_count = usrp_dev->get_rx_num_channels(); @@ -85,36 +87,33 @@ int uhd_wrap::wrap_read(TIMESTAMP *timestamp) extern "C" void *uhdwrap_open(struct ipc_sk_if_open_req *open_req) { - unsigned int rx_sps, tx_sps; + actual_cfg.num_chans = open_req->num_chans; + actual_cfg.swap_channels = false; + /* FIXME: this is actually the sps value, not the sample rate! + * sample rate is looked up according to the sps rate by uhd backend */ + actual_cfg.rx_sps = open_req->rx_sample_freq_num / open_req->rx_sample_freq_den; + actual_cfg.tx_sps = open_req->tx_sample_freq_num / open_req->tx_sample_freq_den; /* FIXME: dev arg string* */ /* FIXME: rx frontend bw? */ /* FIXME: tx frontend bw? */ - ReferenceType cref; switch (open_req->clockref) { case FEATURE_MASK_CLOCKREF_EXTERNAL: - cref = ReferenceType::REF_EXTERNAL; + actual_cfg.clock_ref = ReferenceType::REF_EXTERNAL; break; case FEATURE_MASK_CLOCKREF_INTERNAL: default: - cref = ReferenceType::REF_INTERNAL; + actual_cfg.clock_ref = ReferenceType::REF_INTERNAL; break; } - std::vector<std::string> tx_paths; - std::vector<std::string> rx_paths; for (unsigned int i = 0; i < open_req->num_chans; i++) { - tx_paths.push_back(open_req->chan_info[i].tx_path); - rx_paths.push_back(open_req->chan_info[i].rx_path); + actual_cfg.chans[i].rx_path = open_req->chan_info[i].tx_path; + actual_cfg.chans[i].tx_path = open_req->chan_info[i].rx_path; } - /* FIXME: this is actually the sps value, not the sample rate! - * sample rate is looked up according to the sps rate by uhd backend */ - rx_sps = open_req->rx_sample_freq_num / open_req->rx_sample_freq_den; - tx_sps = open_req->tx_sample_freq_num / open_req->tx_sample_freq_den; - uhd_wrap *uhd_wrap_dev = - new uhd_wrap(tx_sps, rx_sps, RadioDevice::NORMAL, open_req->num_chans, 0.0, tx_paths, rx_paths); - uhd_wrap_dev->open("", cref, false); + uhd_wrap *uhd_wrap_dev = new uhd_wrap(RadioDevice::NORMAL, &actual_cfg); + uhd_wrap_dev->open(); return uhd_wrap_dev; } diff --git a/Transceiver52M/device/ipc/uhdwrap.h b/Transceiver52M/device/ipc/uhdwrap.h index e235fe7..44cd9aa 100644 --- a/Transceiver52M/device/ipc/uhdwrap.h +++ b/Transceiver52M/device/ipc/uhdwrap.h @@ -41,7 +41,7 @@ class uhd_wrap : public uhd_device { // void ipc_sock_close() override {}; int wrap_read(TIMESTAMP *timestamp); - virtual int open(const std::string &args, int ref, bool swap_channels) override; + virtual int open() override; // bool start() override; // bool stop() override; diff --git a/Transceiver52M/device/lms/LMSDevice.cpp b/Transceiver52M/device/lms/LMSDevice.cpp index 20b9856..7c220d2 100644 --- a/Transceiver52M/device/lms/LMSDevice.cpp +++ b/Transceiver52M/device/lms/LMSDevice.cpp @@ -23,7 +23,6 @@ #include <map> -#include "trx_vty.h" #include "Logger.h" #include "Threads.h" #include "LMSDevice.h" @@ -32,6 +31,7 @@ #include <lime/LimeSuite.h> extern "C" { +#include "trx_vty.h" #include "osmo_signal.h" #include <osmocom/core/utils.h> } @@ -40,8 +40,6 @@ extern "C" { #include "config.h" #endif -using namespace std; - #define MAX_ANTENNA_LIST_SIZE 10 #define GSM_CARRIER_BW 270000.0 /* 270kHz */ #define LMS_MIN_BW_SUPPORTED 2.5e6 /* 2.5mHz, minimum supported by LMS */ @@ -54,58 +52,28 @@ using namespace std; #define LMS_DEV_SDR_MINI_PREFIX_NAME "LimeSDR-Mini" #define LMS_DEV_NET_MICRO_PREFIX_NAME "LimeNET-Micro" -/* Device parameter descriptor */ -struct dev_desc { - /* Does LimeSuite allow switching the clock source for this device? - * LimeSDR-Mini does not have switches but needs soldering to select - * external/internal clock. Any call to LMS_SetClockFreq() will fail. - */ - bool clock_src_switchable; - /* Does LimeSuite allow using REF_INTERNAL for this device? - * LimeNET-Micro does not like selecting internal clock - */ - bool clock_src_int_usable; - /* Sample rate coef (without having TX/RX samples per symbol into account) */ - double rate; - /* Sample rate coef (without having TX/RX samples per symbol into account), if multi-arfcn is enabled */ - double rate_multiarfcn; - /* Coefficient multiplied by TX sample rate in order to shift Tx time */ - double ts_offset_coef; - /* Coefficient multiplied by TX sample rate in order to shift Tx time, if multi-arfcn is enabled */ - double ts_offset_coef_multiarfcn; - /* Device Name Prefix as presented by LimeSuite API LMS_GetDeviceInfo() */ - std::string name_prefix; -}; -static const std::map<enum lms_dev_type, struct dev_desc> dev_param_map { + +static const dev_map_t dev_param_map { { LMS_DEV_SDR_USB, { true, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_SDR_USB_PREFIX_NAME } }, { LMS_DEV_SDR_MINI, { false, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 8.2e-5, LMS_DEV_SDR_MINI_PREFIX_NAME } }, { LMS_DEV_NET_MICRO, { true, false, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, LMS_DEV_NET_MICRO_PREFIX_NAME } }, { LMS_DEV_UNKNOWN, { true, true, GSMRATE, MCBTS_SPACING, 8.9e-5, 7.9e-5, "UNKNOWN" } }, }; -typedef std::tuple<lms_dev_type, enum gsm_band> dev_band_key; -/* Maximum LimeSuite Tx Gain which can be set/used without distorting the output - * signal, and the resulting real output power measured when that gain is used. - */ -struct dev_band_desc { - double nom_lms_tx_gain; /* dB */ - double nom_out_tx_power; /* dBm */ -}; -typedef std::map<dev_band_key, dev_band_desc>::const_iterator dev_band_map_it; -static const std::map<dev_band_key, dev_band_desc> dev_band_nom_power_param_map { - { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_850), { 73.0, 11.2 } }, - { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_900), { 73.0, 10.8 } }, - { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1800), { 65.0, -3.5 } }, /* FIXME: OS#4583: 1800Mhz is failing above TxGain=65, which is around -3.5dBm (already < 0 dBm) */ - { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1900), { 73.0, 1.7 } }, /* FIXME: OS#4583: 1900MHz is failing in all TxGain values */ - { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_850), { 66.0, 3.1 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */ - { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_900), { 66.0, 2.8 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */ - { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1800), { 66.0, -11.6 } }, /* OS#4583: Any of BAND1 or BAND2 is fine */ - { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1900), { 66.0, -9.2 } }, /* FIXME: OS#4583: Ensure BAND1 is used at startup */ - { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_850), { 71.0, 6.8 } }, - { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_900), { 71.0, 6.8 } }, - { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1800), { 65.0, -10.5 } }, /* OS#4583: TxGain=71 (-4.4dBm) FAIL rms phase errors ~10° */ - { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1900), { 71.0, -6.3 } }, /* FIXME: OS#4583: all FAIL, BAND1/BAND2 rms phase errors >23° */ +static const power_map_t dev_band_nom_power_param_map { + { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_850), { 73.0, 11.2, -6.0 } }, + { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_900), { 73.0, 10.8, -6.0 } }, + { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1800), { 65.0, -3.5, -17.0 } }, /* FIXME: OS#4583: 1800Mhz is failing above TxGain=65, which is around -3.5dBm (already < 0 dBm) */ + { std::make_tuple(LMS_DEV_SDR_USB, GSM_BAND_1900), { 73.0, 1.7, -17.0 } }, /* FIXME: OS#4583: 1900MHz is failing in all TxGain values */ + { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_850), { 66.0, 3.1, -6.0 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */ + { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_900), { 66.0, 2.8, -6.0 } }, /* FIXME: OS#4583: Ensure BAND2 is used at startup */ + { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1800), { 66.0, -11.6, -17.0 } }, /* OS#4583: Any of BAND1 or BAND2 is fine */ + { std::make_tuple(LMS_DEV_SDR_MINI, GSM_BAND_1900), { 66.0, -9.2, -17.0 } }, /* FIXME: OS#4583: Ensure BAND1 is used at startup */ + { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_850), { 71.0, 6.8, -6.0 } }, + { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_900), { 71.0, 6.8, -6.0 } }, + { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1800), { 65.0, -10.5, -17.0 } }, /* OS#4583: TxGain=71 (-4.4dBm) FAIL rms phase errors ~10° */ + { std::make_tuple(LMS_DEV_NET_MICRO, GSM_BAND_1900), { 71.0, -6.3, -17.0 } }, /* FIXME: OS#4583: all FAIL, BAND1/BAND2 rms phase errors >23° */ }; /* So far measurements done for B210 show really close to linear relationship @@ -131,8 +99,8 @@ static enum lms_dev_type parse_dev_type(lms_device_t *m_lms_dev) enum lms_dev_type dev_type = it->first; struct dev_desc desc = it->second; - if (strncmp(device_info->deviceName, desc.name_prefix.c_str(), desc.name_prefix.length()) == 0) { - LOGC(DDEV, INFO) << "Device identified as " << desc.name_prefix; + if (strncmp(device_info->deviceName, desc.desc_str.c_str(), desc.desc_str.length()) == 0) { + LOGC(DDEV, INFO) << "Device identified as " << desc.desc_str; return dev_type; } it++; @@ -140,11 +108,10 @@ static enum lms_dev_type parse_dev_type(lms_device_t *m_lms_dev) return LMS_DEV_UNKNOWN; } -LMSDevice::LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths): - RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths), - m_lms_dev(NULL), started(false), band((enum gsm_band)0), m_dev_type(LMS_DEV_UNKNOWN) +LMSDevice::LMSDevice(InterfaceType iface, const struct trx_cfg *cfg) + : RadioDevice(iface, cfg), + band_manager(m_dev_type, dev_band_nom_power_param_map, dev_param_map, {LMS_DEV_SDR_USB, GSM_BAND_850}), m_lms_dev(NULL), + started(false), m_dev_type(LMS_DEV_UNKNOWN) { LOGC(DDEV, INFO) << "creating LMS device..."; @@ -210,7 +177,7 @@ static void print_range(const char* name, lms_range_t *range) int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::string &args) { unsigned int i, j; - std::vector<string> filters; + std::vector<std::string> filters; filters = comma_delimited_to_vector(args.c_str()); @@ -231,28 +198,7 @@ int info_list_find(lms_info_str_t* info_list, unsigned int count, const std::str return -1; } -void LMSDevice::get_dev_band_desc(dev_band_desc& desc) -{ - dev_band_map_it it; - enum gsm_band req_band = band; - - if (req_band == 0) { - LOGC(DDEV, ERROR) << "Nominal Tx Power requested before Tx Frequency was set! Providing band 900 by default... "; - req_band = GSM_BAND_900; - } - it = dev_band_nom_power_param_map.find(dev_band_key(m_dev_type, req_band)); - if (it == dev_band_nom_power_param_map.end()) { - dev_desc desc = dev_param_map.at(m_dev_type); - LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device " - << desc.name_prefix << " on band " << gsm_band_name(req_band) - << ", using LimeSDR-USB ones as fallback"; - it = dev_band_nom_power_param_map.find(dev_band_key(LMS_DEV_SDR_USB, req_band)); - } - OSMO_ASSERT(it != dev_band_nom_power_param_map.end()) - desc = it->second; -} - -int LMSDevice::open(const std::string &args, int ref, bool swap_channels) +int LMSDevice::open() { lms_info_str_t* info_list; lms_range_t range_sr; @@ -265,11 +211,12 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) LMS_RegisterLogHandler(&lms_log_callback); - if ((n = LMS_GetDeviceList(NULL)) < 0) + if ((rc = LMS_GetDeviceList(NULL)) < 0) LOGC(DDEV, ERROR) << "LMS_GetDeviceList(NULL) failed"; - LOGC(DDEV, INFO) << "Devices found: " << n; - if (n < 1) + LOGC(DDEV, INFO) << "Devices found: " << rc; + if (rc < 1) return -1; + n = rc; info_list = new lms_info_str_t[n]; @@ -279,9 +226,9 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) for (i = 0; i < n; i++) LOGC(DDEV, INFO) << "Device [" << i << "]: " << info_list[i]; - dev_id = info_list_find(info_list, n, args); + dev_id = info_list_find(info_list, n, cfg->dev_args); if (dev_id == -1) { - LOGC(DDEV, ERROR) << "No LMS device found with address '" << args << "'"; + LOGC(DDEV, ERROR) << "No LMS device found with address '" << cfg->dev_args << "'"; delete[] info_list; return -1; } @@ -298,14 +245,15 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) m_dev_type = parse_dev_type(m_lms_dev); dev_desc = dev_param_map.at(m_dev_type); + update_band_dev(m_dev_type); - if ((ref != REF_EXTERNAL) && (ref != REF_INTERNAL)){ + if ((cfg->clock_ref != REF_EXTERNAL) && (cfg->clock_ref != REF_INTERNAL)) { LOGC(DDEV, ERROR) << "Invalid reference type"; goto out_close; } /* if reference clock is external, setup must happen _before_ calling LMS_Init */ - if (ref == REF_EXTERNAL) { + if (cfg->clock_ref == REF_EXTERNAL) { LOGC(DDEV, INFO) << "Setting External clock reference to 10MHz"; /* FIXME: Assume an external 10 MHz reference clock. make external reference frequency configurable */ @@ -320,7 +268,7 @@ int LMSDevice::open(const std::string &args, int ref, bool swap_channels) } /* if reference clock is internal, setup must happen _after_ calling LMS_Init */ - if (ref == REF_INTERNAL) { + if (cfg->clock_ref == REF_INTERNAL) { LOGC(DDEV, INFO) << "Setting Internal clock reference"; /* Internal freq param is not used */ if (!do_clock_src_freq(REF_INTERNAL, 0)) @@ -456,6 +404,8 @@ bool LMSDevice::stop() LMS_DestroyStream(m_lms_dev, &m_lms_stream_rx[i]); } + band_reset(); + started = false; return true; } @@ -471,8 +421,8 @@ bool LMSDevice::do_clock_src_freq(enum ReferenceType ref, double freq) break; case REF_INTERNAL: if (!dev_desc.clock_src_int_usable) { - LOGC(DDEV, ERROR) << "Device type " << dev_desc.name_prefix - << " doesn't support internal reference clock"; + LOGC(DDEV, ERROR) + << "Device type " << dev_desc.desc_str << " doesn't support internal reference clock"; return false; } /* According to lms using LMS_CLOCK_EXTREF with a @@ -490,8 +440,8 @@ bool LMSDevice::do_clock_src_freq(enum ReferenceType ref, double freq) if (LMS_SetClockFreq(m_lms_dev, lms_clk_id, freq) < 0) return false; } else { - LOGC(DDEV, INFO) << "Device type " << dev_desc.name_prefix - << " doesn't support switching clock source through SW"; + LOGC(DDEV, INFO) + << "Device type " << dev_desc.desc_str << " doesn't support switching clock source through SW"; } return true; @@ -561,6 +511,21 @@ double LMSDevice::setRxGain(double dB, size_t chan) return rx_gains[chan]; } +double LMSDevice::rssiOffset(size_t chan) +{ + double rssiOffset; + dev_band_desc desc; + + if (chan >= rx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + get_dev_band_desc(desc); + rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel; + return rssiOffset; +} + double LMSDevice::setPowerAttenuation(int atten, size_t chan) { double tx_power, dB; @@ -969,9 +934,6 @@ bool LMSDevice::updateAlignment(TIMESTAMP timestamp) bool LMSDevice::setTxFreq(double wFreq, size_t chan) { - uint16_t req_arfcn; - enum gsm_band req_band; - if (chan >= chans) { LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; return false; @@ -979,29 +941,14 @@ bool LMSDevice::setTxFreq(double wFreq, size_t chan) LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz"; - req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100 , 0); - if (req_arfcn == 0xffff) { - LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Tx Frequency " << wFreq / 1000 << " kHz"; + if (!update_band_from_freq(wFreq, chan, true)) return false; - } - if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) { - LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Tx Frequency " << wFreq - << " Hz (ARFCN " << req_arfcn << " )"; - return false; - } - - if (band != 0 && req_band != band) { - LOGCHAN(chan, DDEV, ALERT) << "Requesting Tx Frequency " << wFreq - << " Hz different from previous band " << gsm_band_name(band); - return false; - } if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_TX, chan, wFreq) < 0) { LOGCHAN(chan, DDEV, ERROR) << "Error setting Tx Freq to " << wFreq << " Hz"; return false; } - band = req_band; return true; } @@ -1009,6 +956,9 @@ bool LMSDevice::setRxFreq(double wFreq, size_t chan) { LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz"; + if (!update_band_from_freq(wFreq, chan, false)) + return false; + if (LMS_SetLOFrequency(m_lms_dev, LMS_CH_RX, chan, wFreq) < 0) { LOGCHAN(chan, DDEV, ERROR) << "Error setting Rx Freq to " << wFreq << " Hz"; return false; @@ -1017,18 +967,15 @@ bool LMSDevice::setRxFreq(double wFreq, size_t chan) return true; } -RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, - InterfaceType iface, size_t chans, double lo_offset, - const std::vector < std::string > &tx_paths, - const std::vector < std::string > &rx_paths) +RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) { - if (tx_sps != rx_sps) { - LOGC(DDEV, ERROR) << "LMS Requires tx_sps == rx_sps"; + if (cfg->tx_sps != cfg->rx_sps) { + LOGC(DDEV, ERROR) << "LMS requires tx_sps == rx_sps"; return NULL; } - if (lo_offset != 0.0) { + if (cfg->offset != 0.0) { LOGC(DDEV, ERROR) << "LMS doesn't support lo_offset"; return NULL; } - return new LMSDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); + return new LMSDevice(type, cfg); } diff --git a/Transceiver52M/device/lms/LMSDevice.h b/Transceiver52M/device/lms/LMSDevice.h index 7af09e2..2e5ca4c 100644 --- a/Transceiver52M/device/lms/LMSDevice.h +++ b/Transceiver52M/device/lms/LMSDevice.h @@ -18,11 +18,13 @@ #ifndef _LMS_DEVICE_H_ #define _LMS_DEVICE_H_ +#include <map> #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "radioDevice.h" +#include "bandmanager.h" #include "smpl_buf.h" #include <sys/time.h> @@ -52,10 +54,52 @@ enum lms_dev_type { LMS_DEV_UNKNOWN, }; -struct dev_band_desc; +struct dev_band_desc { + /* Maximum LimeSuite Tx Gain which can be set/used without distorting + the output * signal, and the resulting real output power measured + when that gain is used. + */ + double nom_lms_tx_gain; /* dB */ + double nom_out_tx_power; /* dBm */ + /* Factor used to infer base real RSSI offset on the Rx path based on current + configured RxGain. The resulting rssiOffset is added to the per burst + calculated energy in upper layers. These values were empirically + found and may change based on multiple factors, see OS#4468. + Correct measured values only provided for LimeSDR-USB so far. + rssiOffset = rxGain + rxgain2rssioffset_rel; + */ + double rxgain2rssioffset_rel; /* dB */ +}; + +/* Device parameter descriptor */ +struct dev_desc { + /* Does LimeSuite allow switching the clock source for this device? + * LimeSDR-Mini does not have switches but needs soldering to select + * external/internal clock. Any call to LMS_SetClockFreq() will fail. + */ + bool clock_src_switchable; + /* Does LimeSuite allow using REF_INTERNAL for this device? + * LimeNET-Micro does not like selecting internal clock + */ + bool clock_src_int_usable; + /* Sample rate coef (without having TX/RX samples per symbol into account) */ + double rate; + /* Sample rate coef (without having TX/RX samples per symbol into account), if multi-arfcn is enabled */ + double rate_multiarfcn; + /* Coefficient multiplied by TX sample rate in order to shift Tx time */ + double ts_offset_coef; + /* Coefficient multiplied by TX sample rate in order to shift Tx time, if multi-arfcn is enabled */ + double ts_offset_coef_multiarfcn; + /* Device Name Prefix as presented by LimeSuite API LMS_GetDeviceInfo() */ + std::string desc_str; +}; + +using dev_band_key_t = std::tuple<lms_dev_type, gsm_band>; +using power_map_t = std::map<dev_band_key_t, dev_band_desc>; +using dev_map_t = std::map<lms_dev_type, struct dev_desc>; /** A class to handle a LimeSuite supported device */ -class LMSDevice:public RadioDevice { +class LMSDevice:public RadioDevice, public band_manager<power_map_t, dev_map_t> { private: lms_device_t *m_lms_dev; @@ -72,7 +116,6 @@ private: TIMESTAMP ts_initial, ts_offset; std::vector<double> tx_gains, rx_gains; - enum gsm_band band; enum lms_dev_type m_dev_type; @@ -84,28 +127,25 @@ private: void update_stream_stats_rx(size_t chan, bool *overrun); void update_stream_stats_tx(size_t chan, bool *underrun); bool do_clock_src_freq(enum ReferenceType ref, double freq); - void get_dev_band_desc(dev_band_desc& desc); - public: /** Object constructor */ - LMSDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths); - ~LMSDevice(); + LMSDevice(InterfaceType iface, const struct trx_cfg *cfg); + ~LMSDevice(); - /** Instantiate the LMS */ - int open(const std::string &args, int ref, bool swap_channels); + /** Instantiate the LMS */ + int open(); - /** Start the LMS */ - bool start(); + /** Start the LMS */ + bool start(); - /** Stop the LMS */ - bool stop(); + /** Stop the LMS */ + bool stop(); - enum TxWindowType getWindowType() { - return TX_WINDOW_LMS1; - } + enum TxWindowType getWindowType() + { + return TX_WINDOW_LMS1; + } /** Read samples from the LMS. @@ -173,6 +213,7 @@ public: /** return minimum Rx Gain **/ double minRxGain(void); + double rssiOffset(size_t chan); double setPowerAttenuation(int atten, size_t chan); double getPowerAttenuation(size_t chan = 0); diff --git a/Transceiver52M/device/lms/Makefile.am b/Transceiver52M/device/lms/Makefile.am index 77fd0e9..6665077 100644 --- a/Transceiver52M/device/lms/Makefile.am +++ b/Transceiver52M/device/lms/Makefile.am @@ -1,7 +1,7 @@ include $(top_srcdir)/Makefile.common AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common -AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LMS_CFLAGS) +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LMS_CFLAGS) noinst_HEADERS = LMSDevice.h diff --git a/Transceiver52M/device/uhd/Makefile.am b/Transceiver52M/device/uhd/Makefile.am index ab63a4a..21df631 100644 --- a/Transceiver52M/device/uhd/Makefile.am +++ b/Transceiver52M/device/uhd/Makefile.am @@ -1,7 +1,7 @@ include $(top_srcdir)/Makefile.common AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common -AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS) +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS) noinst_HEADERS = UHDDevice.h diff --git a/Transceiver52M/device/uhd/UHDDevice.cpp b/Transceiver52M/device/uhd/UHDDevice.cpp index 143a061..85e9e38 100644 --- a/Transceiver52M/device/uhd/UHDDevice.cpp +++ b/Transceiver52M/device/uhd/UHDDevice.cpp @@ -91,19 +91,7 @@ extern "C" { * USRP1 with timestamps is not supported by UHD. */ -/* Device Type, Tx-SPS, Rx-SPS */ -typedef std::tuple<uhd_dev_type, int, int> dev_key; - -/* Device parameter descriptor */ -struct dev_desc { - unsigned channels; - double mcr; - double rate; - double offset; - std::string str; -}; - -static const std::map<dev_key, dev_desc> dev_param_map { +static const dev_map_t dev_param_map { { std::make_tuple(USRP2, 1, 1), { 1, 0.0, 390625, 1.2184e-4, "N2XX 1 SPS" } }, { std::make_tuple(USRP2, 4, 1), { 1, 0.0, 390625, 7.6547e-5, "N2XX 4/1 Tx/Rx SPS" } }, { std::make_tuple(USRP2, 4, 4), { 1, 0.0, 390625, 4.6080e-5, "N2XX 4 SPS" } }, @@ -129,24 +117,15 @@ static const std::map<dev_key, dev_desc> dev_param_map { { std::make_tuple(B2XX_MCBTS, 4, 4), { 1, 51.2e6, MCBTS_SPACING*4, B2XX_TIMING_MCBTS, "B200/B210 4 SPS Multi-ARFCN" } }, }; -typedef std::tuple<uhd_dev_type, enum gsm_band> dev_band_key; -/* Maximum UHD Tx Gain which can be set/used without distorting the - output signal, and the resulting real output power measured when that - gain is used. Correct measured values only provided for B210 so far. */ -struct dev_band_desc { - double nom_uhd_tx_gain; /* dB */ - double nom_out_tx_power; /* dBm */ -}; -typedef std::map<dev_band_key, dev_band_desc>::const_iterator dev_band_map_it; -static const std::map<dev_band_key, dev_band_desc> dev_band_nom_power_param_map { - { std::make_tuple(B200, GSM_BAND_850), { 89.75, 13.3 } }, - { std::make_tuple(B200, GSM_BAND_900), { 89.75, 13.3 } }, - { std::make_tuple(B200, GSM_BAND_1800), { 89.75, 7.5 } }, - { std::make_tuple(B200, GSM_BAND_1900), { 89.75, 7.7 } }, - { std::make_tuple(B210, GSM_BAND_850), { 89.75, 13.3 } }, - { std::make_tuple(B210, GSM_BAND_900), { 89.75, 13.3 } }, - { std::make_tuple(B210, GSM_BAND_1800), { 89.75, 7.5 } }, - { std::make_tuple(B210, GSM_BAND_1900), { 89.75, 7.7 } }, +static const power_map_t dev_band_nom_power_param_map { + { std::make_tuple(B200, GSM_BAND_850), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(B200, GSM_BAND_900), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(B200, GSM_BAND_1800), { 89.75, 7.5, -11.0 } }, + { std::make_tuple(B200, GSM_BAND_1900), { 89.75, 7.7, -11.0 } }, + { std::make_tuple(B210, GSM_BAND_850), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(B210, GSM_BAND_900), { 89.75, 13.3, -7.5 } }, + { std::make_tuple(B210, GSM_BAND_1800), { 89.75, 7.5, -11.0 } }, + { std::make_tuple(B210, GSM_BAND_1900), { 89.75, 7.7, -11.0 } }, }; void *async_event_loop(uhd_device *dev) @@ -227,15 +206,10 @@ static double TxPower2TxGain(const dev_band_desc &desc, double tx_power_dbm) return desc.nom_uhd_tx_gain - (desc.nom_out_tx_power - tx_power_dbm); } -uhd_device::uhd_device(size_t tx_sps, size_t rx_sps, - InterfaceType iface, size_t chan_num, double lo_offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths) - : RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths), - rx_gain_min(0.0), rx_gain_max(0.0), - band((enum gsm_band)0), tx_spp(0), rx_spp(0), - started(false), aligned(false), drop_cnt(0), - prev_ts(0,0), ts_initial(0), ts_offset(0), async_event_thrd(NULL) +uhd_device::uhd_device(InterfaceType iface, const struct trx_cfg *cfg) + : RadioDevice(iface, cfg), band_manager(dev_band_nom_power_param_map, dev_param_map), rx_gain_min(0.0), + rx_gain_max(0.0), tx_spp(0), rx_spp(0), started(false), aligned(false), drop_cnt(0), prev_ts(0, 0), + ts_initial(0), ts_offset(0), async_event_thrd(NULL) { } @@ -247,27 +221,6 @@ uhd_device::~uhd_device() delete rx_buffers[i]; } -void uhd_device::get_dev_band_desc(dev_band_desc& desc) -{ - dev_band_map_it it; - enum gsm_band req_band = band; - - if (req_band == 0) { - LOGC(DDEV, ERROR) << "Nominal Tx Power requested before Tx Frequency was set! Providing band 900 by default... "; - req_band = GSM_BAND_900; - } - it = dev_band_nom_power_param_map.find(dev_band_key(dev_type, req_band)); - if (it == dev_band_nom_power_param_map.end()) { - dev_desc desc = dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps)); - LOGC(DDEV, ERROR) << "No Tx Power measurements exist for device " - << desc.str << " on band " << gsm_band_name(req_band) - << ", using B210 ones as fallback"; - it = dev_band_nom_power_param_map.find(dev_band_key(B210, req_band)); - } - OSMO_ASSERT(it != dev_band_nom_power_param_map.end()) - desc = it->second; -} - void uhd_device::init_gains() { double tx_gain_min, tx_gain_max; @@ -332,7 +285,7 @@ void uhd_device::set_rates() rx_rate = usrp_dev->get_rx_rate(); ts_offset = static_cast<TIMESTAMP>(desc.offset * rx_rate); - LOGC(DDEV, INFO) << "Rates configured for " << desc.str; + LOGC(DDEV, INFO) << "Rates configured for " << desc.desc_str; } double uhd_device::setRxGain(double db, size_t chan) @@ -342,6 +295,9 @@ double uhd_device::setRxGain(double db, size_t chan) return 0.0f; } + if (cfg->overrides.ul_gain_override) + return rx_gains[chan]; + usrp_dev->set_rx_gain(db, chan); rx_gains[chan] = usrp_dev->get_rx_gain(chan); @@ -360,6 +316,21 @@ double uhd_device::getRxGain(size_t chan) return rx_gains[chan]; } +double uhd_device::rssiOffset(size_t chan) +{ + double rssiOffset; + dev_band_desc desc; + + if (chan >= rx_gains.size()) { + LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + get_dev_band_desc(desc); + rssiOffset = rx_gains[chan] + desc.rxgain2rssioffset_rel; + return rssiOffset; +} + double uhd_device::setPowerAttenuation(int atten, size_t chan) { double tx_power, db; dev_band_desc desc; @@ -369,6 +340,9 @@ double uhd_device::setPowerAttenuation(int atten, size_t chan) { return 0.0f; } + if (cfg->overrides.dl_gain_override) + return atten; // ensures caller does not apply digital attenuation + get_dev_band_desc(desc); tx_power = desc.nom_out_tx_power - atten; db = TxPower2TxGain(desc, tx_power); @@ -520,9 +494,10 @@ void uhd_device::set_channels(bool swap) } } -int uhd_device::open(const std::string &args, int ref, bool swap_channels) +int uhd_device::open() { const char *refstr; + int clock_lock_attempts = 15; /* Register msg handler. Different APIs depending on UHD version */ #ifdef USE_UHD_3_11 @@ -535,10 +510,10 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) #endif // Find UHD devices - uhd::device_addr_t addr(args); + uhd::device_addr_t addr(cfg->dev_args); uhd::device_addrs_t dev_addrs = uhd::device::find(addr); if (dev_addrs.size() == 0) { - LOGC(DDEV, ALERT) << "No UHD devices found with address '" << args << "'"; + LOGC(DDEV, ALERT) << "No UHD devices found with address '" << cfg->dev_args << "'"; return -1; } @@ -547,7 +522,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) try { usrp_dev = uhd::usrp::multi_usrp::make(addr); } catch(uhd::key_error::exception &e) { - LOGC(DDEV, ALERT) << "UHD make failed, device " << args << ", exception:\n" << e.what(); + LOGC(DDEV, ALERT) << "UHD make failed, device " << cfg->dev_args << ", exception:\n" << e.what(); return -1; } @@ -555,14 +530,16 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) if (!parse_dev_type()) return -1; + update_band_dev(dev_key(dev_type, tx_sps, rx_sps)); + if ((dev_type == E3XX) && !uhd_e3xx_version_chk()) { LOGC(DDEV, ALERT) << "E3XX requires UHD 003.009.000 or greater"; return -1; } try { - set_channels(swap_channels); - } catch (const std::exception &e) { + set_channels(cfg->swap_channels); + } catch (const std::exception &e) { LOGC(DDEV, ALERT) << "Channel setting failed - " << e.what(); return -1; } @@ -578,7 +555,7 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) rx_gains.resize(chans); rx_buffers.resize(chans); - switch (ref) { + switch (cfg->clock_ref) { case REF_INTERNAL: refstr = "internal"; break; @@ -595,6 +572,19 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) usrp_dev->set_clock_source(refstr); + std::vector<std::string> sensor_names = usrp_dev->get_mboard_sensor_names(); + if (std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end()) { + LOGC(DDEV, INFO) << "Waiting for clock reference lock (max " << clock_lock_attempts << "s)..." << std::flush; + while (!usrp_dev->get_mboard_sensor("ref_locked", 0).to_bool() && clock_lock_attempts--) + sleep(1); + + if (!clock_lock_attempts) { + LOGC(DDEV, ALERT) << "Locking to external 10Mhz failed!"; + return -1; + } + } + LOGC(DDEV, INFO) << "Selected clock source is " << usrp_dev->get_clock_source(0); + try { set_rates(); } catch (const std::exception &e) { @@ -644,6 +634,32 @@ int uhd_device::open(const std::string &args, int ref, bool swap_channels) // Print configuration LOGC(DDEV, INFO) << "Device configuration: " << usrp_dev->get_pp_string(); + if (cfg->overrides.dl_freq_override) { + uhd::tune_request_t treq_tx = uhd::tune_request_t(cfg->overrides.dl_freq, 0); + auto tres = usrp_dev->set_tx_freq(treq_tx, 0); + tx_freqs[0] = usrp_dev->get_tx_freq(0); + LOGCHAN(0, DDEV, INFO) << "OVERRIDE set_freq(" << tx_freqs[0] << ", TX): " << tres.to_pp_string() << std::endl; + } + + if (cfg->overrides.ul_freq_override) { + uhd::tune_request_t treq_rx = uhd::tune_request_t(cfg->overrides.ul_freq, 0); + auto tres = usrp_dev->set_rx_freq(treq_rx, 0); + rx_freqs[0] = usrp_dev->get_rx_freq(0); + LOGCHAN(0, DDEV, INFO) << "OVERRIDE set_freq(" << rx_freqs[0] << ", RX): " << tres.to_pp_string() << std::endl; + } + + if (cfg->overrides.ul_gain_override) { + usrp_dev->set_rx_gain(cfg->overrides.ul_gain, 0); + rx_gains[0] = usrp_dev->get_rx_gain(0); + LOGCHAN(0, DDEV, INFO) << " OVERRIDE RX gain:" << rx_gains[0] << std::endl; + } + + if (cfg->overrides.dl_gain_override) { + usrp_dev->set_tx_gain(cfg->overrides.dl_gain, 0); + tx_gains[0] = usrp_dev->get_tx_gain(0); + LOGCHAN(0, DDEV, INFO) << " OVERRIDE TX gain:" << tx_gains[0] << std::endl; + } + if (iface == MULTI_ARFCN) return MULTI_ARFCN; @@ -754,6 +770,8 @@ bool uhd_device::stop() for (size_t i = 0; i < rx_buffers.size(); i++) rx_buffers[i]->reset(); + band_reset(); + started = false; return true; } @@ -992,17 +1010,22 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx) { std::vector<double> freqs; uhd::tune_result_t tres; + std::string str_dir = tx ? "Tx" : "Rx"; + + if (cfg->overrides.dl_freq_override || cfg->overrides.ul_freq_override) + return true; + + if (!update_band_from_freq(freq, chan, tx)) + return false; + uhd::tune_request_t treq = select_freq(freq, chan, tx); - std::string str_dir; if (tx) { tres = usrp_dev->set_tx_freq(treq, chan); tx_freqs[chan] = 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); - str_dir = "Rx"; } LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << str_dir << "): " << tres.to_pp_string() << std::endl; @@ -1032,37 +1055,12 @@ bool uhd_device::set_freq(double freq, size_t chan, bool tx) bool uhd_device::setTxFreq(double wFreq, size_t chan) { - uint16_t req_arfcn; - enum gsm_band req_band; - if (chan >= tx_freqs.size()) { LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; return false; } - ScopedLock lock(tune_lock); - - req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100 , 0); - if (req_arfcn == 0xffff) { - LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Tx Frequency " << wFreq / 1000 << " kHz"; - return false; - } - if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) { - LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Tx Frequency " << wFreq - << " Hz (ARFCN " << req_arfcn << " )"; - return false; - } - if (band != 0 && req_band != band) { - LOGCHAN(chan, DDEV, ALERT) << "Requesting Tx Frequency " << wFreq - << " Hz different from previous band " << gsm_band_name(band); - return false; - } - - if (!set_freq(wFreq, chan, true)) - return false; - - band = req_band; - return true; + return set_freq(wFreq, chan, true); } bool uhd_device::setRxFreq(double wFreq, size_t chan) @@ -1071,7 +1069,6 @@ bool uhd_device::setRxFreq(double wFreq, size_t chan) LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan; return false; } - ScopedLock lock(tune_lock); return set_freq(wFreq, chan, false); } @@ -1321,11 +1318,8 @@ std::string uhd_device::str_code(uhd::async_metadata_t metadata) } #ifndef IPCMAGIC -RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, - InterfaceType iface, size_t chans, double lo_offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths) +RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) { - return new uhd_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); + return new uhd_device(type, cfg); } #endif diff --git a/Transceiver52M/device/uhd/UHDDevice.h b/Transceiver52M/device/uhd/UHDDevice.h index c159e63..f5e5232 100644 --- a/Transceiver52M/device/uhd/UHDDevice.h +++ b/Transceiver52M/device/uhd/UHDDevice.h @@ -30,6 +30,7 @@ #include "config.h" #endif +#include "bandmanager.h" #include "radioDevice.h" #include "smpl_buf.h" @@ -56,7 +57,33 @@ enum uhd_dev_type { LIMESDR, }; -struct dev_band_desc; +struct dev_band_desc { + /* Maximum UHD Tx Gain which can be set/used without distorting the + output signal, and the resulting real output power measured when that + gain is used. Correct measured values only provided for B210 so far. */ + double nom_uhd_tx_gain; /* dB */ + double nom_out_tx_power; /* dBm */ + /* Factor used to infer base real RSSI offset on the Rx path based on current + configured RxGain. The resulting rssiOffset is added to the per burst + calculated energy in upper layers. These values were empirically + found and may change based on multiple factors, see OS#4468. + rssiOffset = rxGain + rxgain2rssioffset_rel; + */ + double rxgain2rssioffset_rel; /* dB */ +}; + +struct dev_desc { + unsigned channels; + double mcr; + double rate; + double offset; + std::string desc_str; +}; + +using dev_key = std::tuple<uhd_dev_type, int, int>; +using dev_band_key = std::tuple<uhd_dev_type, enum gsm_band>; +using power_map_t = std::map<dev_band_key, dev_band_desc>; +using dev_map_t = std::map<dev_key, dev_desc>; /* uhd_device - UHD implementation of the Device interface. Timestamped samples @@ -65,19 +92,19 @@ struct dev_band_desc; Events and errors such as underruns are reported asynchronously by the device and received in a separate thread. */ -class uhd_device : public RadioDevice { +class uhd_device : public RadioDevice, public band_manager<power_map_t, dev_map_t> { public: - 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, - const std::vector<std::string>& rx_paths); - ~uhd_device(); - - int open(const std::string &args, int ref, bool swap_channels); - bool start(); - bool stop(); - bool restart(); - enum TxWindowType getWindowType() { return tx_window; } + uhd_device(InterfaceType iface, const struct trx_cfg *cfg); + ~uhd_device(); + + int open(); + bool start(); + bool stop(); + bool restart(); + enum TxWindowType getWindowType() + { + return tx_window; + } int readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun); @@ -100,6 +127,7 @@ public: double getRxGain(size_t chan); double maxRxGain(void) { return rx_gain_max; } double minRxGain(void) { return rx_gain_min; } + double rssiOffset(size_t chan); double setPowerAttenuation(int atten, size_t chan); double getPowerAttenuation(size_t chan = 0); @@ -146,7 +174,6 @@ protected: std::vector<double> tx_gains, rx_gains; std::vector<double> tx_freqs, rx_freqs; - enum gsm_band band; size_t tx_spp, rx_spp; bool started; @@ -175,8 +202,6 @@ protected: uhd::tune_request_t select_freq(double wFreq, size_t chan, bool tx); bool set_freq(double freq, size_t chan, bool tx); - void get_dev_band_desc(dev_band_desc& desc); Thread *async_event_thrd; - Mutex tune_lock; }; diff --git a/Transceiver52M/device/usrp1/Makefile.am b/Transceiver52M/device/usrp1/Makefile.am index 24760f7..ba93c7e 100644 --- a/Transceiver52M/device/usrp1/Makefile.am +++ b/Transceiver52M/device/usrp1/Makefile.am @@ -1,7 +1,7 @@ include $(top_srcdir)/Makefile.common AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common -AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(USRP_CFLAGS) +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(USRP_CFLAGS) rev2dir = $(datadir)/usrp/rev2 rev4dir = $(datadir)/usrp/rev4 diff --git a/Transceiver52M/device/usrp1/USRPDevice.cpp b/Transceiver52M/device/usrp1/USRPDevice.cpp index 5c40aa8..63a9bcc 100644 --- a/Transceiver52M/device/usrp1/USRPDevice.cpp +++ b/Transceiver52M/device/usrp1/USRPDevice.cpp @@ -60,11 +60,7 @@ const dboardConfigType dboardConfig = TXA_RXB; const double USRPDevice::masterClockRate = 52.0e6; -USRPDevice::USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, - size_t chan_num, double lo_offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths): - RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths) +USRPDevice::USRPDevice(InterfaceType iface, const struct trx_cfg *cfg) : RadioDevice(iface, cfg) { LOGC(DDEV, INFO) << "creating USRP device..."; @@ -94,7 +90,7 @@ USRPDevice::USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, #endif } -int USRPDevice::open(const std::string &, int, bool) +int USRPDevice::open() { writeLock.unlock(); @@ -587,8 +583,7 @@ bool USRPDevice::updateAlignment(TIMESTAMP timestamp) { #ifndef SWLOOPBACK short data[] = {0x00,0x02,0x00,0x00}; - uint32_t *wordPtr = (uint32_t *) data; - *wordPtr = host_to_usrp_u32(*wordPtr); + /* FIXME: big endian */ bool tmpUnderrun; std::vector<short *> buf(1, data); @@ -659,22 +654,19 @@ bool USRPDevice::setTxFreq(double wFreq) { return true;}; bool USRPDevice::setRxFreq(double wFreq) { return true;}; #endif -RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, - InterfaceType iface, size_t chans, double lo_offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths) +RadioDevice *RadioDevice::make(InterfaceType type, const struct trx_cfg *cfg) { - if (tx_sps != rx_sps) { - LOGC(DDEV, ERROR) << "USRP1 requires tx_sps == rx_sps"; - return NULL; - } - if (chans != 1) { - LOGC(DDEV, ERROR) << "USRP1 supports only 1 channel"; - return NULL; - } - if (lo_offset != 0.0) { - LOGC(DDEV, ERROR) << "USRP1 doesn't support lo_offset"; - return NULL; - } - return new USRPDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); + if (cfg->tx_sps != cfg->rx_sps) { + LOGC(DDEV, ERROR) << "USRP1 requires tx_sps == rx_sps"; + return NULL; + } + if (cfg->num_chans != 1) { + LOGC(DDEV, ERROR) << "USRP1 supports only 1 channel"; + return NULL; + } + if (cfg->offset != 0.0) { + LOGC(DDEV, ERROR) << "USRP1 doesn't support lo_offset"; + return NULL; + } + return new USRPDevice(type, cfg); } diff --git a/Transceiver52M/device/usrp1/USRPDevice.h b/Transceiver52M/device/usrp1/USRPDevice.h index f761dc0..4957ee6 100644 --- a/Transceiver52M/device/usrp1/USRPDevice.h +++ b/Transceiver52M/device/usrp1/USRPDevice.h @@ -104,20 +104,21 @@ private: public: /** Object constructor */ - USRPDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset, - const std::vector<std::string>& tx_paths, - const std::vector<std::string>& rx_paths); + USRPDevice(InterfaceType iface, const struct trx_cfg *cfg); - /** Instantiate the USRP */ - int open(const std::string &, int, bool); + /** Instantiate the USRP */ + int open(); - /** Start the USRP */ - bool start(); + /** Start the USRP */ + bool start(); - /** Stop the USRP */ - bool stop(); + /** Stop the USRP */ + bool stop(); - enum TxWindowType getWindowType() { return TX_WINDOW_USRP1; } + enum TxWindowType getWindowType() + { + return TX_WINDOW_USRP1; + } /** Read samples from the USRP. @@ -174,6 +175,8 @@ private: /** return minimum Rx Gain **/ double minRxGain(void); + double rssiOffset(size_t chan) { return 0.0f; } /* FIXME: not implemented */ + double setPowerAttenuation(int atten, size_t chan); double getPowerAttenuation(size_t chan=0); diff --git a/Transceiver52M/grgsm_vitac/constants.h b/Transceiver52M/grgsm_vitac/constants.h new file mode 100644 index 0000000..27bf6f4 --- /dev/null +++ b/Transceiver52M/grgsm_vitac/constants.h @@ -0,0 +1,149 @@ +#pragma once +/* -*- c++ -*- */ +/* + * @file + * @author (C) 2009-2017 by Piotr Krysik <ptrkrysik@gmail.com> + * @section LICENSE + * + * Gr-gsm 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, or (at your option) + * any later version. + * + * Gr-gsm 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 gr-gsm; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include <complex> + +#define gr_complex std::complex<float> + + +#define GSM_SYMBOL_RATE (1625000.0/6.0) //symbols per second +#define GSM_SYMBOL_PERIOD (1.0/GSM_SYMBOL_RATE) //seconds per symbol + +//Burst timing +#define TAIL_BITS 3 +#define GUARD_BITS 8 +#define GUARD_FRACTIONAL 0.25 //fractional part of guard period +#define GUARD_PERIOD GUARD_BITS + GUARD_FRACTIONAL +#define DATA_BITS 57 //size of 1 data block in normal burst +#define STEALING_BIT 1 +#define N_TRAIN_BITS 26 +#define N_SYNC_BITS 64 +#define N_ACCESS_BITS 41 +#define USEFUL_BITS 142 //(2*(DATA_BITS+STEALING_BIT) + N_TRAIN_BITS ) +#define FCCH_BITS USEFUL_BITS +#define BURST_SIZE (USEFUL_BITS+2*TAIL_BITS) +#define ACCESS_BURST_SIZE 88 +#define PROCESSED_CHUNK BURST_SIZE+2*GUARD_PERIOD + +#define SCH_DATA_LEN 39 +#define TS_BITS (TAIL_BITS+USEFUL_BITS+TAIL_BITS+GUARD_BITS) //a full TS (156 bits) +#define TS_PER_FRAME 8 +#define FRAME_BITS (TS_PER_FRAME * TS_BITS + 2) // 156.25 * 8 +#define FCCH_POS TAIL_BITS +#define SYNC_POS (TAIL_BITS + 39) +#define TRAIN_POS ( TAIL_BITS + (DATA_BITS+STEALING_BIT) + 5) //first 5 bits of a training sequence + //aren't used for channel impulse response estimation +#define TRAIN_BEGINNING 5 +#define SAFETY_MARGIN 6 // + +#define FCCH_HITS_NEEDED (USEFUL_BITS - 4) +#define FCCH_MAX_MISSES 1 +#define FCCH_MAX_FREQ_OFFSET 100 + +#define CHAN_IMP_RESP_LENGTH 5 + +#define MAX_SCH_ERRORS 10 //maximum number of subsequent sch errors after which gsm receiver goes to find_next_fcch state + +typedef enum { empty, fcch_burst, sch_burst, normal_burst, rach_burst, dummy, dummy_or_normal, normal_or_noise } burst_type; +typedef enum { unknown, multiframe_26, multiframe_51 } multiframe_type; + +static const unsigned char SYNC_BITS[] = { + 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, + 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1 +}; + +static const unsigned char ACCESS_BITS [] = { + 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, + 0, 1, 1, 1, 1, 0, 0, 0 +}; + +const unsigned FCCH_FRAMES[] = { 0, 10, 20, 30, 40 }; +const unsigned SCH_FRAMES[] = { 1, 11, 21, 31, 41 }; + +const unsigned BCCH_FRAMES[] = { 2, 3, 4, 5 }; //!!the receiver shouldn't care about logical + //!!channels so this will be removed from this header +const unsigned TEST_CCH_FRAMES[] = { 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 44, 45, 46, 47, 48, 49 }; +const unsigned TRAFFIC_CHANNEL_F[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 }; +const unsigned TEST51[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 }; + + +#define TSC0 0 +#define TSC1 1 +#define TSC2 2 +#define TSC3 3 +#define TSC4 4 +#define TSC5 5 +#define TSC6 6 +#define TSC7 7 +#define TS_DUMMY 8 + +#define TRAIN_SEQ_NUM 9 + +#define TIMESLOT0 0 +#define TIMESLOT1 1 +#define TIMESLOT2 2 +#define TIMESLOT3 3 +#define TIMESLOT4 4 +#define TIMESLOT5 5 +#define TIMESLOT6 6 +#define TIMESLOT7 7 + + +static const unsigned char train_seq[TRAIN_SEQ_NUM][N_TRAIN_BITS] = { + {0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1}, + {0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1}, + {0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0}, + {0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0}, + {0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1}, + {0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0}, + {1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1}, + {1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0}, + {0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1} // DUMMY +}; + + +//Dummy burst 0xFB 76 0A 4E 09 10 1F 1C 5C 5C 57 4A 33 39 E9 F1 2F A8 +static const unsigned char dummy_burst[] = { + 0, 0, 0, + 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, + 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, + 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 0, 0, + + 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, + 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, + 0, 0, 0, 1, 0, 1, + + 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, + 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, + 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0 +}; diff --git a/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp b/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp new file mode 100644 index 0000000..2016541 --- /dev/null +++ b/Transceiver52M/grgsm_vitac/grgsm_vitac.cpp @@ -0,0 +1,305 @@ +/* -*- c++ -*- */ +/* + * @file + * @author (C) 2009-2017 by Piotr Krysik <ptrkrysik@gmail.com> + * @author Contributions by sysmocom - s.f.m.c. GmbH / Eric Wild <ewild@sysmocom.de> + * @section LICENSE + * + * Gr-gsm 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, or (at your option) + * any later version. + * + * Gr-gsm 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 gr-gsm; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "constants.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <complex> + + +#include <algorithm> +#include <string.h> +#include <iostream> +#include <numeric> +#include <vector> +#include <fstream> + +#include "viterbi_detector.h" +#include "grgsm_vitac.h" + +gr_complex d_acc_training_seq[N_ACCESS_BITS]; ///<encoded training sequence of a RACH burst +gr_complex d_sch_training_seq[N_SYNC_BITS]; ///<encoded training sequence of a SCH burst +gr_complex d_norm_training_seq[TRAIN_SEQ_NUM][N_TRAIN_BITS]; ///<encoded training sequences of a normal and dummy burst +const int d_chan_imp_length = CHAN_IMP_RESP_LENGTH; + +void initvita() +{ + /** + * Prepare SCH sequence bits + * + * (TS_BITS + 2 * GUARD_PERIOD) + * Burst and two guard periods + * (one guard period is an arbitrary overlap) + */ + gmsk_mapper(SYNC_BITS, N_SYNC_BITS, d_sch_training_seq, gr_complex(0.0, -1.0)); + for (auto &i : d_sch_training_seq) + i = conj(i); + + /* ab */ + gmsk_mapper(ACCESS_BITS, N_ACCESS_BITS, d_acc_training_seq, gr_complex(0.0, -1.0)); + for (auto &i : d_acc_training_seq) + i = conj(i); + + /* Prepare bits of training sequences */ + for (int i = 0; i < TRAIN_SEQ_NUM; i++) { + /** + * If first bit of the sequence is 0 + * => first symbol is 1, else -1 + */ + gr_complex startpoint = train_seq[i][0] == 0 ? gr_complex(1.0, 0.0) : gr_complex(-1.0, 0.0); + gmsk_mapper(train_seq[i], N_TRAIN_BITS, d_norm_training_seq[i], startpoint); + for (auto &i : d_norm_training_seq[i]) + i = conj(i); + } +} + +template <unsigned int burst_size> +NO_UBSAN static void detect_burst_generic(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, + char *output_binary, int ss) +{ + std::vector<gr_complex> rhh_temp(CHAN_IMP_RESP_LENGTH * d_OSR); + unsigned int stop_states[2] = { 4, 12 }; + gr_complex filtered_burst[burst_size]; + gr_complex rhh[CHAN_IMP_RESP_LENGTH]; + float output[burst_size]; + int start_state = ss; + + autocorrelation(chan_imp_resp, &rhh_temp[0], d_chan_imp_length * d_OSR); + for (int ii = 0; ii < d_chan_imp_length; ii++) + rhh[ii] = conj(rhh_temp[ii * d_OSR]); + + mafi(&input[burst_start], burst_size, chan_imp_resp, d_chan_imp_length * d_OSR, filtered_burst); + + viterbi_detector(filtered_burst, burst_size, rhh, start_state, stop_states, 2, output); + + for (unsigned int i = 0; i < burst_size; i++) + output_binary[i] = output[i] > 0 ? -127 : 127; // pre flip bits! +} + +NO_UBSAN void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, + int ss) +{ + return detect_burst_generic<BURST_SIZE>(input, chan_imp_resp, burst_start, output_binary, ss); +} +NO_UBSAN void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, + int ss) +{ + return detect_burst_generic<8 + 41 + 36 + 3>(input, chan_imp_resp, burst_start, output_binary, ss); +} + +NO_UBSAN void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary) +{ + return detect_burst_nb(input, chan_imp_resp, burst_start, output_binary, 3); +} +NO_UBSAN void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary) +{ + return detect_burst_ab(input, chan_imp_resp, burst_start, output_binary, 3); +} + +void gmsk_mapper(const unsigned char *input, int nitems, gr_complex *gmsk_output, gr_complex start_point) +{ + gr_complex j = gr_complex(0.0, 1.0); + gmsk_output[0] = start_point; + + int previous_symbol = 2 * input[0] - 1; + int current_symbol; + int encoded_symbol; + + for (int i = 1; i < nitems; i++) { + /* Change bits representation to NRZ */ + current_symbol = 2 * input[i] - 1; + + /* Differentially encode */ + encoded_symbol = current_symbol * previous_symbol; + + /* And do GMSK mapping */ + gmsk_output[i] = j * gr_complex(encoded_symbol, 0.0) * gmsk_output[i - 1]; + + previous_symbol = current_symbol; + } +} + +gr_complex correlate_sequence(const gr_complex *sequence, int length, const gr_complex *input) +{ + gr_complex result(0.0, 0.0); + + for (int ii = 0; ii < length; ii++) + result += sequence[ii] * input[ii * d_OSR]; + + return conj(result) / gr_complex(length, 0); +} + +/* Computes autocorrelation for positive arguments */ +inline void autocorrelation(const gr_complex *input, gr_complex *out, int nitems) +{ + for (int k = nitems - 1; k >= 0; k--) { + out[k] = gr_complex(0, 0); + for (int i = k; i < nitems; i++) + out[k] += input[i] * conj(input[i - k]); + } +} + +inline void mafi(const gr_complex *input, int nitems, gr_complex *filter, int filter_length, gr_complex *output) +{ + for (int n = 0; n < nitems; n++) { + int a = n * d_OSR; + output[n] = 0; + + for (int ii = 0; ii < filter_length; ii++) { + if ((a + ii) >= nitems * d_OSR) + break; + + output[n] += input[a + ii] * filter[ii]; + } + } +} + +int get_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, int search_start_pos, int search_stop_pos, + gr_complex *tseq, int tseqlen, float *corr_max) +{ + const int num_search_windows = search_stop_pos - search_start_pos; + const int power_search_window_len = d_chan_imp_length * d_OSR; + std::vector<float> window_energy_buffer; + std::vector<float> power_buffer; + std::vector<gr_complex> correlation_buffer; + + power_buffer.reserve(num_search_windows); + correlation_buffer.reserve(num_search_windows); + window_energy_buffer.reserve(num_search_windows); + + for (int ii = 0; ii < num_search_windows; ii++) { + gr_complex correlation = correlate_sequence(tseq, tseqlen, &input[search_start_pos + ii]); + correlation_buffer.push_back(correlation); + power_buffer.push_back(std::pow(abs(correlation), 2)); + } + + /* Compute window energies */ + float windowSum = 0; + + // first window + for (int i = 0; i < power_search_window_len; i++) { + windowSum += power_buffer[i]; + } + window_energy_buffer.push_back(windowSum); + + // slide windows + for (int i = power_search_window_len; i < num_search_windows; i++) { + windowSum += power_buffer[i] - power_buffer[i - power_search_window_len]; + window_energy_buffer.push_back(windowSum); + } + + int strongest_window_nr = std::max_element(window_energy_buffer.begin(), window_energy_buffer.end()) - + window_energy_buffer.begin(); + + float max_correlation = 0; + for (int ii = 0; ii < power_search_window_len; ii++) { + gr_complex correlation = correlation_buffer[strongest_window_nr + ii]; + if (abs(correlation) > max_correlation) + max_correlation = abs(correlation); + chan_imp_resp[ii] = correlation; + } + + *corr_max = max_correlation; + + /** + * Compute first sample position, which corresponds + * to the first sample of the impulse response + */ + return search_start_pos + strongest_window_nr; +} + +/* +8 ext tail bits +41 sync seq +36 encrypted bits +3 tail bits +68.25 extended tail bits (!) + +center at 8+5 (actually known tb -> known isi, start at 8?) FIXME +*/ +int get_access_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int max_delay) +{ + const int search_center = 8 + 5; + const int search_start_pos = (search_center - 5) * d_OSR + 1; + const int search_stop_pos = (search_center + 5 + d_chan_imp_length + max_delay) * d_OSR; + const auto tseq = &d_acc_training_seq[TRAIN_BEGINNING]; + const auto tseqlen = N_ACCESS_BITS - (2 * TRAIN_BEGINNING); + return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) - + search_center * d_OSR; +} + +/* + +3 + 57 + 1 + 26 + 1 + 57 + 3 + 8.25 + +search center = 3 + 57 + 1 + 5 (due to tsc 5+16+5 split) +this is +-5 samples around (+5 beginning) of truncated t16 tsc + +*/ +int get_norm_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int bcc) +{ + const int search_center = TRAIN_POS; + const int search_start_pos = (search_center - 5) * d_OSR + 1; + const int search_stop_pos = (search_center + 5 + d_chan_imp_length) * d_OSR; + const auto tseq = &d_norm_training_seq[bcc][TRAIN_BEGINNING]; + const auto tseqlen = N_TRAIN_BITS - (2 * TRAIN_BEGINNING); + return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) - + search_center * d_OSR; +} + +/* + +3 tail | 39 data | 64 tsc | 39 data | 3 tail | 8.25 guard +start 3+39 - 10 +end 3+39 + SYNC_SEARCH_RANGE + +*/ +int get_sch_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp) +{ + const int search_center = SYNC_POS + TRAIN_BEGINNING; + const int search_start_pos = (search_center - 10) * d_OSR; + const int search_stop_pos = (search_center + SYNC_SEARCH_RANGE) * d_OSR; + const auto tseq = &d_sch_training_seq[TRAIN_BEGINNING]; + const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING); + + // strongest_window_nr + chan_imp_resp_center + SYNC_POS *d_OSR - 48 * d_OSR - 2 * d_OSR + 2 ; + float corr_max; + return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, &corr_max) - + search_center * d_OSR; + ; +} + +int get_sch_buffer_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, unsigned int len, float *corr_max) +{ + const auto tseqlen = N_SYNC_BITS - (2 * TRAIN_BEGINNING); + const int search_center = SYNC_POS + TRAIN_BEGINNING; + const int search_start_pos = 0; + // FIXME: proper end offset + const int search_stop_pos = len - (N_SYNC_BITS * 8); + auto tseq = &d_sch_training_seq[TRAIN_BEGINNING]; + + return get_chan_imp_resp(input, chan_imp_resp, search_start_pos, search_stop_pos, tseq, tseqlen, corr_max) - + search_center * d_OSR; +}
\ No newline at end of file diff --git a/Transceiver52M/grgsm_vitac/grgsm_vitac.h b/Transceiver52M/grgsm_vitac/grgsm_vitac.h new file mode 100644 index 0000000..c1e470c --- /dev/null +++ b/Transceiver52M/grgsm_vitac/grgsm_vitac.h @@ -0,0 +1,89 @@ +#pragma once +/* -*- c++ -*- */ +/* + * @file + * @author (C) 2009-2017 by Piotr Krysik <ptrkrysik@gmail.com> + * @section LICENSE + * + * Gr-gsm 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, or (at your option) + * any later version. + * + * Gr-gsm 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 gr-gsm; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include <vector> +#include "constants.h" + +/* may only be used for for the DEFINITIONS! +* see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91664 +*/ +#if defined(__has_attribute) +#if __has_attribute(target_clones) && defined(__x86_64) && true +#define MULTI_VER_TARGET_ATTR __attribute__((target_clones("avx", "sse4.2", "sse3", "sse2", "sse", "default"))) +#else +#define MULTI_VER_TARGET_ATTR +#endif +#endif + +/* ... but apparently clang disagrees... */ +#if defined(__clang__) +#define MULTI_VER_TARGET_ATTR_CLANGONLY MULTI_VER_TARGET_ATTR +#else +#define MULTI_VER_TARGET_ATTR_CLANGONLY +#endif + +/* ancient gcc < 8 has no attribute, clang always pretends to be gcc 4 */ +#if !defined(__clang__) && __GNUC__ < 8 +#define NO_UBSAN __attribute__((no_sanitize_undefined)) +#else +#if defined(__has_attribute) +#if __has_attribute(no_sanitize) +#define NO_UBSAN __attribute__((no_sanitize("undefined"))) +#endif +#else +#define NO_UBSAN +#endif +#endif + +#define SYNC_SEARCH_RANGE 30 +const int d_OSR(4); + +void initvita(); + +int process_vita_burst(gr_complex *input, int tsc, unsigned char *output_binary); +int process_vita_sc_burst(gr_complex *input, int tsc, unsigned char *output_binary, int *offset); + +void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, int ss); +void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary, int ss); +void detect_burst_nb(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary); +void detect_burst_ab(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary); + +void gmsk_mapper(const unsigned char *input, int nitems, gr_complex *gmsk_output, gr_complex start_point); +gr_complex correlate_sequence(const gr_complex *sequence, int length, const gr_complex *input); +inline void autocorrelation(const gr_complex *input, gr_complex *out, int nitems); +inline void mafi(const gr_complex *input, int nitems, gr_complex *filter, int filter_length, gr_complex *output); +int get_sch_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp); +int get_norm_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int bcc); +int get_sch_buffer_chan_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, unsigned int len, float *corr_max); +int get_access_imp_resp(const gr_complex *input, gr_complex *chan_imp_resp, float *corr_max, int max_delay); + +enum class btype { NB, SCH }; +struct fdata { + btype t; + unsigned int fn; + int tn; + int bcc; + std::string fpath; + std::vector<gr_complex> data; + unsigned int data_start_offset; +};
\ No newline at end of file diff --git a/Transceiver52M/grgsm_vitac/viterbi_detector.cc b/Transceiver52M/grgsm_vitac/viterbi_detector.cc new file mode 100644 index 0000000..4ac4505 --- /dev/null +++ b/Transceiver52M/grgsm_vitac/viterbi_detector.cc @@ -0,0 +1,392 @@ +/* -*- c++ -*- */ +/* + * @file + * @author (C) 2009 by Piotr Krysik <ptrkrysik@gmail.com> + * @section LICENSE + * + * Gr-gsm 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, or (at your option) + * any later version. + * + * Gr-gsm 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 gr-gsm; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +/* + * viterbi_detector: + * This part does the detection of received sequnece. + * Employed algorithm is viterbi Maximum Likehood Sequence Estimation. + * At this moment it gives hard decisions on the output, but + * it was designed with soft decisions in mind. + * + * SYNTAX: void viterbi_detector( + * const gr_complex * input, + * unsigned int samples_num, + * gr_complex * rhh, + * unsigned int start_state, + * const unsigned int * stop_states, + * unsigned int stops_num, + * float * output) + * + * INPUT: input: Complex received signal afted matched filtering. + * samples_num: Number of samples in the input table. + * rhh: The autocorrelation of the estimated channel + * impulse response. + * start_state: Number of the start point. In GSM each burst + * starts with sequence of three bits (0,0,0) which + * indicates start point of the algorithm. + * stop_states: Table with numbers of possible stop states. + * stops_num: Number of possible stop states + * + * + * OUTPUT: output: Differentially decoded hard output of the algorithm: + * -1 for logical "0" and 1 for logical "1" + * + * SUB_FUNC: none + * + * TEST(S): Tested with real world normal burst. + */ + +#include "constants.h" +#include <cmath> + +#define PATHS_NUM (1 << (CHAN_IMP_RESP_LENGTH-1)) + +void viterbi_detector(const gr_complex * input, unsigned int samples_num, gr_complex * rhh, unsigned int start_state, const unsigned int * stop_states, unsigned int stops_num, float * output) +{ + float increment[8]; + float path_metrics1[16]; + float path_metrics2[16]; + float paths_difference; + float * new_path_metrics; + float * old_path_metrics; + float * tmp; + float trans_table[BURST_SIZE][16]; + float pm_candidate1, pm_candidate2; + bool real_imag; + float input_symbol_real, input_symbol_imag; + unsigned int i, sample_nr; + +/* +* Setup first path metrics, so only state pointed by start_state is possible. +* Start_state metric is equal to zero, the rest is written with some very low value, +* which makes them practically impossible to occur. +*/ + for(i=0; i<PATHS_NUM; i++){ + path_metrics1[i]=(-10e30); + } + path_metrics1[start_state]=0; + +/* +* Compute Increment - a table of values which does not change for subsequent input samples. +* Increment is table of reference levels for computation of branch metrics: +* branch metric = (+/-)received_sample (+/-) reference_level +*/ + increment[0] = -rhh[1].imag() -rhh[2].real() -rhh[3].imag() +rhh[4].real(); + increment[1] = rhh[1].imag() -rhh[2].real() -rhh[3].imag() +rhh[4].real(); + increment[2] = -rhh[1].imag() +rhh[2].real() -rhh[3].imag() +rhh[4].real(); + increment[3] = rhh[1].imag() +rhh[2].real() -rhh[3].imag() +rhh[4].real(); + increment[4] = -rhh[1].imag() -rhh[2].real() +rhh[3].imag() +rhh[4].real(); + increment[5] = rhh[1].imag() -rhh[2].real() +rhh[3].imag() +rhh[4].real(); + increment[6] = -rhh[1].imag() +rhh[2].real() +rhh[3].imag() +rhh[4].real(); + increment[7] = rhh[1].imag() +rhh[2].real() +rhh[3].imag() +rhh[4].real(); + + +/* +* Computation of path metrics and decisions (Add-Compare-Select). +* It's composed of two parts: one for odd input samples (imaginary numbers) +* and one for even samples (real numbers). +* Each part is composed of independent (parallelisable) statements like +* this one: +* pm_candidate1 = old_path_metrics[0] -input_symbol_imag +increment[2]; +* pm_candidate2 = old_path_metrics[8] -input_symbol_imag -increment[5]; +* paths_difference=pm_candidate2-pm_candidate1; +* new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; +* trans_table[sample_nr][1] = paths_difference; +* This is very good point for optimisations (SIMD or OpenMP) as it's most time +* consuming part of this function. +*/ + sample_nr=0; + old_path_metrics=path_metrics1; + new_path_metrics=path_metrics2; + while(sample_nr<samples_num){ + //Processing imag states + real_imag=1; + input_symbol_imag = input[sample_nr].imag(); + + pm_candidate1 = old_path_metrics[0] +input_symbol_imag -increment[2]; + pm_candidate2 = old_path_metrics[8] +input_symbol_imag +increment[5]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[0]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][0] = paths_difference; + + pm_candidate1 = old_path_metrics[0] -input_symbol_imag +increment[2]; + pm_candidate2 = old_path_metrics[8] -input_symbol_imag -increment[5]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][1] = paths_difference; + + pm_candidate1 = old_path_metrics[1] +input_symbol_imag -increment[3]; + pm_candidate2 = old_path_metrics[9] +input_symbol_imag +increment[4]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[2]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][2] = paths_difference; + + pm_candidate1 = old_path_metrics[1] -input_symbol_imag +increment[3]; + pm_candidate2 = old_path_metrics[9] -input_symbol_imag -increment[4]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[3]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][3] = paths_difference; + + pm_candidate1 = old_path_metrics[2] +input_symbol_imag -increment[0]; + pm_candidate2 = old_path_metrics[10] +input_symbol_imag +increment[7]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[4]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][4] = paths_difference; + + pm_candidate1 = old_path_metrics[2] -input_symbol_imag +increment[0]; + pm_candidate2 = old_path_metrics[10] -input_symbol_imag -increment[7]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[5]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][5] = paths_difference; + + pm_candidate1 = old_path_metrics[3] +input_symbol_imag -increment[1]; + pm_candidate2 = old_path_metrics[11] +input_symbol_imag +increment[6]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[6]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][6] = paths_difference; + + pm_candidate1 = old_path_metrics[3] -input_symbol_imag +increment[1]; + pm_candidate2 = old_path_metrics[11] -input_symbol_imag -increment[6]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[7]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][7] = paths_difference; + + pm_candidate1 = old_path_metrics[4] +input_symbol_imag -increment[6]; + pm_candidate2 = old_path_metrics[12] +input_symbol_imag +increment[1]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[8]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][8] = paths_difference; + + pm_candidate1 = old_path_metrics[4] -input_symbol_imag +increment[6]; + pm_candidate2 = old_path_metrics[12] -input_symbol_imag -increment[1]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[9]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][9] = paths_difference; + + pm_candidate1 = old_path_metrics[5] +input_symbol_imag -increment[7]; + pm_candidate2 = old_path_metrics[13] +input_symbol_imag +increment[0]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[10]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][10] = paths_difference; + + pm_candidate1 = old_path_metrics[5] -input_symbol_imag +increment[7]; + pm_candidate2 = old_path_metrics[13] -input_symbol_imag -increment[0]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[11]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][11] = paths_difference; + + pm_candidate1 = old_path_metrics[6] +input_symbol_imag -increment[4]; + pm_candidate2 = old_path_metrics[14] +input_symbol_imag +increment[3]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[12]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][12] = paths_difference; + + pm_candidate1 = old_path_metrics[6] -input_symbol_imag +increment[4]; + pm_candidate2 = old_path_metrics[14] -input_symbol_imag -increment[3]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[13]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][13] = paths_difference; + + pm_candidate1 = old_path_metrics[7] +input_symbol_imag -increment[5]; + pm_candidate2 = old_path_metrics[15] +input_symbol_imag +increment[2]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[14]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][14] = paths_difference; + + pm_candidate1 = old_path_metrics[7] -input_symbol_imag +increment[5]; + pm_candidate2 = old_path_metrics[15] -input_symbol_imag -increment[2]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[15]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][15] = paths_difference; + tmp=old_path_metrics; + old_path_metrics=new_path_metrics; + new_path_metrics=tmp; + + sample_nr++; + if(sample_nr==samples_num) + break; + + //Processing real states + real_imag=0; + input_symbol_real = input[sample_nr].real(); + + pm_candidate1 = old_path_metrics[0] -input_symbol_real -increment[7]; + pm_candidate2 = old_path_metrics[8] -input_symbol_real +increment[0]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[0]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][0] = paths_difference; + + pm_candidate1 = old_path_metrics[0] +input_symbol_real +increment[7]; + pm_candidate2 = old_path_metrics[8] +input_symbol_real -increment[0]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[1]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][1] = paths_difference; + + pm_candidate1 = old_path_metrics[1] -input_symbol_real -increment[6]; + pm_candidate2 = old_path_metrics[9] -input_symbol_real +increment[1]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[2]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][2] = paths_difference; + + pm_candidate1 = old_path_metrics[1] +input_symbol_real +increment[6]; + pm_candidate2 = old_path_metrics[9] +input_symbol_real -increment[1]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[3]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][3] = paths_difference; + + pm_candidate1 = old_path_metrics[2] -input_symbol_real -increment[5]; + pm_candidate2 = old_path_metrics[10] -input_symbol_real +increment[2]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[4]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][4] = paths_difference; + + pm_candidate1 = old_path_metrics[2] +input_symbol_real +increment[5]; + pm_candidate2 = old_path_metrics[10] +input_symbol_real -increment[2]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[5]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][5] = paths_difference; + + pm_candidate1 = old_path_metrics[3] -input_symbol_real -increment[4]; + pm_candidate2 = old_path_metrics[11] -input_symbol_real +increment[3]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[6]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][6] = paths_difference; + + pm_candidate1 = old_path_metrics[3] +input_symbol_real +increment[4]; + pm_candidate2 = old_path_metrics[11] +input_symbol_real -increment[3]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[7]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][7] = paths_difference; + + pm_candidate1 = old_path_metrics[4] -input_symbol_real -increment[3]; + pm_candidate2 = old_path_metrics[12] -input_symbol_real +increment[4]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[8]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][8] = paths_difference; + + pm_candidate1 = old_path_metrics[4] +input_symbol_real +increment[3]; + pm_candidate2 = old_path_metrics[12] +input_symbol_real -increment[4]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[9]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][9] = paths_difference; + + pm_candidate1 = old_path_metrics[5] -input_symbol_real -increment[2]; + pm_candidate2 = old_path_metrics[13] -input_symbol_real +increment[5]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[10]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][10] = paths_difference; + + pm_candidate1 = old_path_metrics[5] +input_symbol_real +increment[2]; + pm_candidate2 = old_path_metrics[13] +input_symbol_real -increment[5]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[11]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][11] = paths_difference; + + pm_candidate1 = old_path_metrics[6] -input_symbol_real -increment[1]; + pm_candidate2 = old_path_metrics[14] -input_symbol_real +increment[6]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[12]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][12] = paths_difference; + + pm_candidate1 = old_path_metrics[6] +input_symbol_real +increment[1]; + pm_candidate2 = old_path_metrics[14] +input_symbol_real -increment[6]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[13]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][13] = paths_difference; + + pm_candidate1 = old_path_metrics[7] -input_symbol_real -increment[0]; + pm_candidate2 = old_path_metrics[15] -input_symbol_real +increment[7]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[14]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][14] = paths_difference; + + pm_candidate1 = old_path_metrics[7] +input_symbol_real +increment[0]; + pm_candidate2 = old_path_metrics[15] +input_symbol_real -increment[7]; + paths_difference=pm_candidate2-pm_candidate1; + new_path_metrics[15]=(paths_difference<0) ? pm_candidate1 : pm_candidate2; + trans_table[sample_nr][15] = paths_difference; + + tmp=old_path_metrics; + old_path_metrics=new_path_metrics; + new_path_metrics=tmp; + + sample_nr++; + } + +/* +* Find the best from the stop states by comparing their path metrics. +* Not every stop state is always possible, so we are searching in +* a subset of them. +*/ + unsigned int best_stop_state; + float stop_state_metric, max_stop_state_metric; + best_stop_state = stop_states[0]; + max_stop_state_metric = old_path_metrics[best_stop_state]; + for(i=1; i< stops_num; i++){ + stop_state_metric = old_path_metrics[stop_states[i]]; + if(stop_state_metric > max_stop_state_metric){ + max_stop_state_metric = stop_state_metric; + best_stop_state = stop_states[i]; + } + } + +/* +* This table was generated with hope that it gives a litle speedup during +* traceback stage. +* Received bit is related to the number of state in the trellis. +* I've numbered states so their parity (number of ones) is related +* to a received bit. +*/ + static const unsigned int parity_table[PATHS_NUM] = { 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, }; + +/* +* Table of previous states in the trellis diagram. +* For GMSK modulation every state has two previous states. +* Example: +* previous_state_nr1 = prev_table[current_state_nr][0] +* previous_state_nr2 = prev_table[current_state_nr][1] +*/ + static const unsigned int prev_table[PATHS_NUM][2] = { {0,8}, {0,8}, {1,9}, {1,9}, {2,10}, {2,10}, {3,11}, {3,11}, {4,12}, {4,12}, {5,13}, {5,13}, {6,14}, {6,14}, {7,15}, {7,15}, }; + +/* +* Traceback and differential decoding of received sequence. +* Decisions stored in trans_table are used to restore best path in the trellis. +*/ + sample_nr=samples_num; + unsigned int state_nr=best_stop_state; + unsigned int decision; + bool out_bit=0; + + while(sample_nr>0){ + sample_nr--; + decision = (trans_table[sample_nr][state_nr]>0); + + if(decision != out_bit) + output[sample_nr]=-trans_table[sample_nr][state_nr]; + else + output[sample_nr]=trans_table[sample_nr][state_nr]; + + out_bit = out_bit ^ real_imag ^ parity_table[state_nr]; + state_nr = prev_table[state_nr][decision]; + real_imag = !real_imag; + } +} diff --git a/Transceiver52M/grgsm_vitac/viterbi_detector.h b/Transceiver52M/grgsm_vitac/viterbi_detector.h new file mode 100644 index 0000000..92bc2a8 --- /dev/null +++ b/Transceiver52M/grgsm_vitac/viterbi_detector.h @@ -0,0 +1,64 @@ +/* -*- c++ -*- */ +/* + * @file + * @author (C) 2009 Piotr Krysik <ptrkrysik@gmail.com> + * @section LICENSE + * + * Gr-gsm 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, or (at your option) + * any later version. + * + * Gr-gsm 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 gr-gsm; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +/* + * viterbi_detector: + * This part does the detection of received sequnece. + * Employed algorithm is viterbi Maximum Likehood Sequence Estimation. + * At this moment it gives hard decisions on the output, but + * it was designed with soft decisions in mind. + * + * SYNTAX: void viterbi_detector( + * const gr_complex * input, + * unsigned int samples_num, + * gr_complex * rhh, + * unsigned int start_state, + * const unsigned int * stop_states, + * unsigned int stops_num, + * float * output) + * + * INPUT: input: Complex received signal afted matched filtering. + * samples_num: Number of samples in the input table. + * rhh: The autocorrelation of the estimated channel + * impulse response. + * start_state: Number of the start point. In GSM each burst + * starts with sequence of three bits (0,0,0) which + * indicates start point of the algorithm. + * stop_states: Table with numbers of possible stop states. + * stops_num: Number of possible stop states + * + * + * OUTPUT: output: Differentially decoded hard output of the algorithm: + * -1 for logical "0" and 1 for logical "1" + * + * SUB_FUNC: none + * + * TEST(S): Tested with real world normal burst. + */ + +#ifndef INCLUDED_VITERBI_DETECTOR_H +#define INCLUDED_VITERBI_DETECTOR_H +#include "constants.h" + +void viterbi_detector(const gr_complex * input, unsigned int samples_num, gr_complex * rhh, unsigned int start_state, const unsigned int * stop_states, unsigned int stops_num, float * output); + +#endif /* INCLUDED_VITERBI_DETECTOR_H */ diff --git a/Transceiver52M/ms/bladerf_specific.h b/Transceiver52M/ms/bladerf_specific.h new file mode 100644 index 0000000..9db8bf0 --- /dev/null +++ b/Transceiver52M/ms/bladerf_specific.h @@ -0,0 +1,479 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "itrq.h" +#include <atomic> +#include <complex> +#include <cstdint> +#include <functional> +#include <iostream> +#include <cassert> +#include <cstring> + +#include <libbladeRF.h> +#include <Timeval.h> +#include <unistd.h> + +const size_t BLADE_BUFFER_SIZE = 1024 * 1; +const size_t BLADE_NUM_BUFFERS = 32 * 1; +const size_t NUM_TRANSFERS = 16 * 2; +const int SAMPLE_SCALE_FACTOR = 15; // actually 16 but sigproc complains about clipping.. + +// see https://en.cppreference.com/w/cpp/language/parameter_pack "Brace-enclosed initializers" example +template <typename Arg, typename... Args> +void expand_args(std::ostream &out, Arg &&arg, Args &&...args) +{ + out << '(' << std::forward<Arg>(arg); + (void)(int[]){ 0, (void((out << "," << std::forward<Args>(args))), 0)... }; + out << ')' << std::endl; +} + +template <class R, class... Args> +using RvalFunc = R (*)(Args...); + +template <class R, class... Args> +R exec_and_check(RvalFunc<R, Args...> func, const char *fname, const char *finame, const char *funcname, int line, + Args... args) +{ + R rval = func(std::forward<Args>(args)...); + if (rval != 0) { + std::cerr << ((rval >= 0) ? "OK:" : bladerf_strerror(rval)) << ':' << finame << ':' << line << ':' + << funcname << ':' << fname; + expand_args(std::cerr, args...); + } + return rval; +} + +// only macros can pass a func name string +#define blade_check(func, ...) exec_and_check(func, #func, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) + +#pragma pack(push, 1) +using blade_sample_type = std::complex<int16_t>; +enum class blade_speed_buffer_type { HS, SS }; +template <blade_speed_buffer_type T> +struct blade_usb_message { + uint32_t reserved; + uint64_t ts; + uint32_t meta_flags; + blade_sample_type d[(T == blade_speed_buffer_type::SS ? 512 : 256) - 4]; +}; + +static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::SS>) == 2048, "blade buffer mismatch!"); +static_assert(sizeof(blade_usb_message<blade_speed_buffer_type::HS>) == 1024, "blade buffer mismatch!"); +template <unsigned int SZ, blade_speed_buffer_type T> +struct blade_otw_buffer { + static_assert((SZ >= 2 && !(SZ % 2)), "min size is 2x usb buffer!"); + blade_usb_message<T> m[SZ]; + int actual_samples_per_msg() + { + return sizeof(blade_usb_message<T>::d) / sizeof(typeof(blade_usb_message<T>::d[0])); + } + int actual_samples_per_buffer() + { + return SZ * actual_samples_per_msg(); + } + int samples_per_buffer() + { + return SZ * sizeof(blade_usb_message<T>) / sizeof(typeof(blade_usb_message<T>::d[0])); + } + int num_msgs_per_buffer() + { + return SZ; + } + auto get_first_ts() + { + return m[0].ts; + } + constexpr auto *getsampleoffset(int ofs) + { + auto full = ofs / actual_samples_per_msg(); + auto rem = ofs % actual_samples_per_msg(); + return &m[full].d[rem]; + } + int readall(blade_sample_type *outaddr) + { + blade_sample_type *addr = outaddr; + for (unsigned int i = 0; i < SZ; i++) { + memcpy(addr, &m[i].d[0], actual_samples_per_msg() * sizeof(blade_sample_type)); + addr += actual_samples_per_msg(); + } + return actual_samples_per_buffer(); + } + int read_n(blade_sample_type *outaddr, int start, int num) + { + assert((start + num) <= actual_samples_per_buffer()); + assert(start >= 0); + + if (!num) + return 0; + + // which buffer? + int start_buf_idx = (start > 0) ? start / actual_samples_per_msg() : 0; + // offset from actual buffer start + auto start_offset_in_buf = (start - (start_buf_idx * actual_samples_per_msg())); + auto samp_rem_in_first_buf = actual_samples_per_msg() - start_offset_in_buf; + auto remaining_first_buf = num > samp_rem_in_first_buf ? samp_rem_in_first_buf : num; + + memcpy(outaddr, &m[start_buf_idx].d[start_offset_in_buf], + remaining_first_buf * sizeof(blade_sample_type)); + outaddr += remaining_first_buf; + + auto remaining = num - remaining_first_buf; + + if (!remaining) + return num; + + start_buf_idx++; + + auto rem_full_bufs = remaining / actual_samples_per_msg(); + remaining -= rem_full_bufs * actual_samples_per_msg(); + + for (int i = 0; i < rem_full_bufs; i++) { + memcpy(outaddr, &m[start_buf_idx++].d[0], actual_samples_per_msg() * sizeof(blade_sample_type)); + outaddr += actual_samples_per_msg(); + } + + if (remaining) + memcpy(outaddr, &m[start_buf_idx].d[0], remaining * sizeof(blade_sample_type)); + return num; + } + int write_n_burst(blade_sample_type *in, int num, uint64_t first_ts) + { + assert(num <= actual_samples_per_buffer()); + int len_rem = num; + for (unsigned int i = 0; i < SZ; i++) { + m[i] = {}; + m[i].ts = first_ts + i * actual_samples_per_msg(); + if (len_rem) { + int max_to_copy = + len_rem > actual_samples_per_msg() ? actual_samples_per_msg() : len_rem; + memcpy(&m[i].d[0], in, max_to_copy * sizeof(blade_sample_type)); + len_rem -= max_to_copy; + in += actual_samples_per_msg(); + } + } + return num; + } +}; +#pragma pack(pop) + +template <unsigned int SZ, blade_speed_buffer_type T> +struct blade_otw_buffer_helper { + static_assert((SZ >= 1024 && ((SZ & (SZ - 1)) == 0)), "only buffer size multiples of 1024 allowed!"); + static blade_otw_buffer<SZ / 512, T> x; +}; + +using dev_buf_t = typeof(blade_otw_buffer_helper<BLADE_BUFFER_SIZE, blade_speed_buffer_type::SS>::x); +// using buf_in_use = blade_otw_buffer<2, blade_speed_buffer_type::SS>; +using bh_fn_t = std::function<int(dev_buf_t *)>; + +template <typename T> +struct blade_hw { + struct bladerf *dev; + struct bladerf_stream *rx_stream; + struct bladerf_stream *tx_stream; + // using pkt2buf = blade_otw_buffer<2, blade_speed_buffer_type::SS>; + using tx_buf_q_type = spsc_cond_timeout<BLADE_NUM_BUFFERS, dev_buf_t *, true, false>; + const unsigned int rxFullScale, txFullScale; + const int rxtxdelay; + + float rxgain, txgain; + static std::atomic<bool> stop_lower_threads_flag; + double rxfreq_cache, txfreq_cache; + + struct ms_trx_config { + int tx_freq; + int rx_freq; + int sample_rate; + int bandwidth; + + public: + ms_trx_config() : tx_freq(881e6), rx_freq(926e6), sample_rate(((1625e3 / 6) * 4)), bandwidth(1e6) + { + } + } cfg; + + struct buf_mgmt { + void **rx_samples; + void **tx_samples; + tx_buf_q_type bufptrqueue; + + } buf_mgmt; + + virtual ~blade_hw() + { + close_device(); + } + blade_hw() + : rxFullScale(2047), txFullScale(2047), rxtxdelay(-60), rxgain(30), txgain(30), rxfreq_cache(0), + txfreq_cache(0) + { + } + + void close_device() + { + if (dev) { + if (tx_stream) { + bladerf_deinit_stream(tx_stream); + } + + if (rx_stream) { + bladerf_deinit_stream(rx_stream); + } + + bladerf_enable_module(dev, BLADERF_MODULE_RX, false); + bladerf_enable_module(dev, BLADERF_MODULE_TX, false); + + bladerf_close(dev); + dev = NULL; + } + } + + int init_device(bh_fn_t rxh, bh_fn_t txh) + { + struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)) * 64, 6 * 64 }, actual; + + bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_DEBUG); + bladerf_set_usb_reset_on_open(true); + + blade_check(bladerf_open, &dev, ""); + if (!dev) { + std::cerr << "open failed, device missing?" << std::endl; + exit(0); + } + if (bladerf_device_speed(dev) != bladerf_dev_speed::BLADERF_DEVICE_SPEED_SUPER) { + std::cerr << "open failed, only superspeed (usb3) supported!" << std::endl; + return -1; + } + + blade_check(bladerf_set_tuning_mode, dev, bladerf_tuning_mode::BLADERF_TUNING_MODE_FPGA); + + bool is_locked; + blade_check(bladerf_set_pll_enable, dev, true); + uint64_t refclock = 10000000UL; + blade_check(bladerf_set_pll_refclk, dev, refclock); + for (int i = 0; i < 20; i++) { + usleep(50 * 1000); + bladerf_get_pll_lock_state(dev, &is_locked); + + if (is_locked) + break; + } + if (!is_locked) { + std::cerr << "unable to lock refclk!" << std::endl; + return -1; + } + + blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_RX(0), &rate, &actual); + blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_TX(0), &rate, &actual); + + blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)cfg.rx_freq); + blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)cfg.tx_freq); + + blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)cfg.bandwidth, + (bladerf_bandwidth *)NULL); + blade_check(bladerf_set_bandwidth, dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)cfg.bandwidth, + (bladerf_bandwidth *)NULL); + + blade_check(bladerf_set_gain_mode, dev, BLADERF_CHANNEL_RX(0), BLADERF_GAIN_MGC); + setRxGain(rxgain, 0); + setTxGain(txgain, 0); + usleep(1000); + + bladerf_set_stream_timeout(dev, BLADERF_TX, 10); + bladerf_set_stream_timeout(dev, BLADERF_RX, 10); + + blade_check(bladerf_init_stream, &rx_stream, dev, getrxcb(rxh), &buf_mgmt.rx_samples, BLADE_NUM_BUFFERS, + BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this); + + blade_check(bladerf_init_stream, &tx_stream, dev, gettxcb(txh), &buf_mgmt.tx_samples, BLADE_NUM_BUFFERS, + BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this); + + for (unsigned int i = 0; i < BLADE_NUM_BUFFERS; i++) { + auto cur_buffer = reinterpret_cast<tx_buf_q_type::elem_t *>(buf_mgmt.tx_samples); + buf_mgmt.bufptrqueue.spsc_push(&cur_buffer[i]); + } + + return 0; + } + + void actually_enable_streams() + { + blade_check(bladerf_enable_module, dev, BLADERF_MODULE_RX, true); + usleep(1000); + blade_check(bladerf_enable_module, dev, BLADERF_MODULE_TX, true); + } + + bool tuneTx(double freq, size_t chan = 0) + { + if (txfreq_cache == freq) + return true; + msleep(15); + blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_TX(0), (bladerf_frequency)freq); + txfreq_cache = freq; + msleep(15); + return true; + }; + bool tuneRx(double freq, size_t chan = 0) + { + if (rxfreq_cache == freq) + return true; + msleep(15); + blade_check(bladerf_set_frequency, dev, BLADERF_CHANNEL_RX(0), (bladerf_frequency)freq); + rxfreq_cache = freq; + msleep(15); + return true; + }; + bool tuneRxOffset(double offset, size_t chan = 0) + { + return true; + }; + + double setRxGain(double dB, size_t chan = 0) + { + rxgain = dB; + msleep(15); + blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_RX(0), (bladerf_gain)dB); + msleep(15); + return dB; + }; + double setTxGain(double dB, size_t chan = 0) + { + txgain = dB; + msleep(15); + blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_TX(0), (bladerf_gain)dB); + msleep(15); + return dB; + }; + int setPowerAttenuation(int atten, size_t chan = 0) + { + return atten; + }; + + static void check_timestamp(dev_buf_t *rcd) + { + static bool first = true; + static uint64_t last_ts; + if (first) { + first = false; + last_ts = rcd->m[0].ts; + } else if (last_ts + rcd->actual_samples_per_buffer() != rcd->m[0].ts) { + std::cerr << "RX Overrun!" << last_ts << " " << rcd->actual_samples_per_buffer() << " " + << last_ts + rcd->actual_samples_per_buffer() << " " << rcd->m[0].ts << std::endl; + last_ts = rcd->m[0].ts; + } else { + last_ts = rcd->m[0].ts; + } + } + + bladerf_stream_cb getrxcb(bh_fn_t rxbh) + { + // C cb -> no capture! + static auto rxbhfn = rxbh; + return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta, + void *samples, size_t num_samples, void *user_data) -> void * { + // struct blade_hw *trx = (struct blade_hw *)user_data; + static int to_skip = 0; + dev_buf_t *rcd = (dev_buf_t *)samples; + + if (stop_lower_threads_flag) + return BLADERF_STREAM_SHUTDOWN; + + if (to_skip < 120) // prevents weird overflows on startup + to_skip++; + else { + check_timestamp(rcd); + rxbhfn(rcd); + } + + return samples; + }; + } + bladerf_stream_cb gettxcb(bh_fn_t txbh) + { + // C cb -> no capture! + static auto txbhfn = txbh; + return [](struct bladerf *dev, struct bladerf_stream *stream, struct bladerf_metadata *meta, + void *samples, size_t num_samples, void *user_data) -> void * { + struct blade_hw *trx = (struct blade_hw *)user_data; + auto ptr = reinterpret_cast<tx_buf_q_type::elem_t>(samples); + + if (samples) // put buffer address back into queue, ready to be reused + trx->buf_mgmt.bufptrqueue.spsc_push(&ptr); + + if (stop_lower_threads_flag) + return BLADERF_STREAM_SHUTDOWN; + + return BLADERF_STREAM_NO_DATA; + }; + } + + auto get_rx_burst_handler_fn(bh_fn_t burst_handler) + { + using thist = decltype(this); + auto fn = [](void *args) -> void * { + thist t = reinterpret_cast<thist>(args); + int status = 0; + if (!stop_lower_threads_flag) + status = bladerf_stream(t->rx_stream, BLADERF_RX_X1); + if (status < 0) + std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl; + + return 0; + }; + return fn; + } + auto get_tx_burst_handler_fn(bh_fn_t burst_handler) + { + using thist = decltype(this); + auto fn = [](void *args) -> void * { + thist t = reinterpret_cast<thist>(args); + int status = 0; + if (!stop_lower_threads_flag) + status = bladerf_stream(t->tx_stream, BLADERF_TX_X1); + if (status < 0) + std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl; + + return 0; + }; + return fn; + } + + void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts) + { + tx_buf_q_type::elem_t rcd; + + // exit by submitting a dummy buffer to assure the libbladerf stream mutex is happy (thread!) + if (!buffer) { + bladerf_submit_stream_buffer(tx_stream, (void *)BLADERF_STREAM_SHUTDOWN, 1000); + return; + } + + //get empty bufer from list + while (!buf_mgmt.bufptrqueue.spsc_pop(&rcd)) + buf_mgmt.bufptrqueue.spsc_prep_pop(); + assert(rcd != nullptr); + + rcd->write_n_burst(buffer, len, ts + rxtxdelay); // blade xa4 specific delay! + blade_check(bladerf_submit_stream_buffer_nb, tx_stream, (void *)rcd); + } +}; diff --git a/Transceiver52M/ms/itrq.h b/Transceiver52M/ms/itrq.h new file mode 100644 index 0000000..69ff515 --- /dev/null +++ b/Transceiver52M/ms/itrq.h @@ -0,0 +1,249 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <atomic> + +#include <condition_variable> +#include <mutex> +#include <sys/eventfd.h> +#include <unistd.h> + +namespace spsc_detail +{ +template <bool block_read, bool block_write> +class spsc_cond_timeout_detail { + std::condition_variable cond_r, cond_w; + std::mutex lr, lw; + std::atomic_int r_flag, w_flag; + const int timeout_ms = 200; + + public: + explicit spsc_cond_timeout_detail() : r_flag(0), w_flag(0) + { + } + + ~spsc_cond_timeout_detail() + { + } + + ssize_t spsc_check_r() + { + std::unique_lock<std::mutex> lk(lr); + if (cond_r.wait_for(lk, std::chrono::milliseconds(timeout_ms), [&] { return r_flag != 0; })) { + r_flag--; + return 1; + } else { + return 0; + } + } + ssize_t spsc_check_w() + { + std::unique_lock<std::mutex> lk(lw); + if (cond_w.wait_for(lk, std::chrono::milliseconds(timeout_ms), [&] { return w_flag != 0; })) { + w_flag--; + return 1; + } else { + return 0; + } + } + void spsc_notify_r() + { + std::unique_lock<std::mutex> lk(lr); + r_flag++; + cond_r.notify_one(); + } + void spsc_notify_w() + { + std::unique_lock<std::mutex> lk(lw); + w_flag++; + cond_w.notify_one(); + } +}; + +template <bool block_read, bool block_write> +class spsc_cond_detail { + std::condition_variable cond_r, cond_w; + std::mutex lr, lw; + std::atomic_int r_flag, w_flag; + + public: + explicit spsc_cond_detail() : r_flag(0), w_flag(0) + { + } + + ~spsc_cond_detail() + { + } + + ssize_t spsc_check_r() + { + std::unique_lock<std::mutex> lk(lr); + while (r_flag == 0) + cond_r.wait(lk); + r_flag--; + return 1; + } + ssize_t spsc_check_w() + { + std::unique_lock<std::mutex> lk(lw); + while (w_flag == 0) + cond_w.wait(lk); + w_flag--; + return 1; + } + void spsc_notify_r() + { + std::unique_lock<std::mutex> lk(lr); + r_flag++; + cond_r.notify_one(); + } + void spsc_notify_w() + { + std::unique_lock<std::mutex> lk(lw); + w_flag++; + cond_w.notify_one(); + } +}; + +// originally designed for select loop integration +template <bool block_read, bool block_write> +class spsc_efd_detail { + int efd_r, efd_w; /* eventfds used to block/notify readers/writers */ + + public: + explicit spsc_efd_detail() + : efd_r(eventfd(0, block_read ? 0 : EFD_NONBLOCK)), efd_w(eventfd(1, block_write ? 0 : EFD_NONBLOCK)) + { + } + + ~spsc_efd_detail() + { + close(efd_r); + close(efd_w); + } + + ssize_t spsc_check_r() + { + uint64_t efdr; + return read(efd_r, &efdr, sizeof(uint64_t)); + } + ssize_t spsc_check_w() + { + uint64_t efdr; + return read(efd_w, &efdr, sizeof(uint64_t)); + } + void spsc_notify_r() + { + uint64_t efdu = 1; + write(efd_r, &efdu, sizeof(uint64_t)); + } + void spsc_notify_w() + { + uint64_t efdu = 1; + write(efd_w, &efdu, sizeof(uint64_t)); + } + int get_r_efd() + { + return efd_r; + } + int get_w_efd() + { + return efd_w; + } +}; + +template <unsigned int SZ, typename ELEM, bool block_read, bool block_write, template <bool, bool> class T> +class spsc : public T<block_read, block_write> { + static_assert(SZ > 0, "queues need a size..."); + std::atomic<unsigned int> readptr; + std::atomic<unsigned int> writeptr; + + ELEM buf[SZ]; + + public: + using base_t = T<block_read, block_write>; + using elem_t = ELEM; + explicit spsc() : readptr(0), writeptr(0) + { + } + + ~spsc() + { + } + + /*! Adds element to the queue by copying the data. + * \param[in] elem input buffer, must match the originally configured queue buffer size!. + * \returns true if queue was not full and element was successfully pushed */ + bool spsc_push(const ELEM *elem) + { + size_t cur_wp, cur_rp; + cur_wp = writeptr.load(std::memory_order_relaxed); + cur_rp = readptr.load(std::memory_order_acquire); + if ((cur_wp + 1) % SZ == cur_rp) { + if (block_write) + base_t::spsc_check_w(); /* blocks, ensures next (!) call succeeds */ + return false; + } + buf[cur_wp] = *elem; + writeptr.store((cur_wp + 1) % SZ, std::memory_order_release); + if (block_read) + base_t::spsc_notify_r(); /* fine after release */ + return true; + } + + /*! Removes element from the queue by copying the data. + * \param[in] elem output buffer, must match the originally configured queue buffer size!. + * \returns true if queue was not empty and element was successfully removed */ + bool spsc_pop(ELEM *elem) + { + size_t cur_wp, cur_rp; + cur_wp = writeptr.load(std::memory_order_acquire); + cur_rp = readptr.load(std::memory_order_relaxed); + + if (cur_wp == cur_rp) /* blocks via prep_pop */ + return false; + + *elem = buf[cur_rp]; + readptr.store((cur_rp + 1) % SZ, std::memory_order_release); + if (block_write) + base_t::spsc_notify_w(); + return true; + } + + /*! Reads the read-fd of the queue, which, depending on settings passed on queue creation, blocks. + * This function can be used to deliberately wait for a non-empty queue on the read side. + * \returns result of reading the fd. */ + ssize_t spsc_prep_pop() + { + return base_t::spsc_check_r(); + } +}; + +} // namespace spsc_detail + +template <unsigned int SZ, typename ELEM, bool block_read, bool block_write> +class spsc_evfd : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_efd_detail> {}; +template <unsigned int SZ, typename ELEM, bool block_read, bool block_write> +class spsc_cond : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_detail> {}; +template <unsigned int SZ, typename ELEM, bool block_read, bool block_write> +class spsc_cond_timeout + : public spsc_detail::spsc<SZ, ELEM, block_read, block_write, spsc_detail::spsc_cond_timeout_detail> {};
\ No newline at end of file diff --git a/Transceiver52M/ms/l1ctl_server.c b/Transceiver52M/ms/l1ctl_server.c new file mode 100644 index 0000000..9e9083f --- /dev/null +++ b/Transceiver52M/ms/l1ctl_server.c @@ -0,0 +1,275 @@ +/* + * OsmocomBB <-> SDR connection bridge + * UNIX socket server for L1CTL + * + * (C) 2013 by Sylvain Munaut <tnt@246tNt.com> + * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2022 by by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * 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 2 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. + * + */ + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#include <sys/un.h> +#include <arpa/inet.h> +#include <sys/socket.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/write_queue.h> + +#include <osmocom/bb/trxcon/logging.h> +#include <osmocom/bb/trxcon/l1ctl_server.h> + +#define LOGP_CLI(cli, cat, level, fmt, args...) LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ##args) + +static int l1ctl_client_read_cb(struct osmo_fd *ofd) +{ + struct l1ctl_client *client = (struct l1ctl_client *)ofd->data; + struct msgb *msg; + uint16_t len; + int rc; + + /* Attempt to read from socket */ + rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD); + if (rc != L1CTL_MSG_LEN_FIELD) { + if (rc <= 0) { + LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: read() failed (rc=%d): %s\n", rc, + strerror(errno)); + } else { + LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: short read\n"); + rc = -EIO; + } + l1ctl_client_conn_close(client); + return rc; + } + + /* Check message length */ + len = ntohs(len); + if (len > L1CTL_LENGTH) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Length is too big: %u\n", len); + return -EINVAL; + } + + /* Allocate a new msg */ + msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, L1CTL_HEADROOM, "l1ctl_rx_msg"); + if (!msg) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n"); + return -ENOMEM; + } + + msg->l1h = msgb_put(msg, len); + rc = read(ofd->fd, msg->l1h, msgb_l1len(msg)); + if (rc != len) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: %s\n", len, rc, strerror(errno)); + msgb_free(msg); + return rc; + } + + /* Debug print */ + LOGP_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len)); + + /* Call L1CTL handler */ + client->server->cfg->conn_read_cb(client, msg); + + return 0; +} + +static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + struct l1ctl_client *client = (struct l1ctl_client *)ofd->data; + int len; + + if (ofd->fd <= 0) + return -EINVAL; + + len = write(ofd->fd, msg->data, msg->len); + if (len != msg->len) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to write data: written (%d) < msg_len (%d)\n", len, + msg->len); + return -1; + } + + return 0; +} + +/* Connection handler */ +static int l1ctl_server_conn_cb(struct osmo_fd *sfd, unsigned int flags) +{ + struct l1ctl_server *server = (struct l1ctl_server *)sfd->data; + struct l1ctl_client *client; + int rc, client_fd; + + client_fd = accept(sfd->fd, NULL, NULL); + if (client_fd < 0) { + LOGP(DL1C, LOGL_ERROR, + "Failed to accept() a new connection: " + "%s\n", + strerror(errno)); + return client_fd; + } + + if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ && + server->num_clients >= server->cfg->num_clients_max) { + LOGP(DL1C, LOGL_NOTICE, + "L1CTL server cannot accept more " + "than %u connection(s)\n", + server->cfg->num_clients_max); + close(client_fd); + return -ENOMEM; + } + + client = talloc_zero(server, struct l1ctl_client); + if (client == NULL) { + LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n"); + close(client_fd); + return -ENOMEM; + } + + /* Init the client's write queue */ + osmo_wqueue_init(&client->wq, 100); + INIT_LLIST_HEAD(&client->wq.bfd.list); + + client->wq.write_cb = &l1ctl_client_write_cb; + client->wq.read_cb = &l1ctl_client_read_cb; + osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0); + + /* Register the client's write queue */ + rc = osmo_fd_register(&client->wq.bfd); + if (rc != 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n"); + close(client->wq.bfd.fd); + talloc_free(client); + return rc; + } + + llist_add_tail(&client->list, &server->clients); + client->id = server->next_client_id++; + client->server = server; + server->num_clients++; + + LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id); + + if (client->server->cfg->conn_accept_cb != NULL) + client->server->cfg->conn_accept_cb(client); + + return 0; +} + +int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg) +{ + uint8_t *len; + + /* Debug print */ + LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len)); + + if (msg->l1h != msg->data) + LOGP_CLI(client, DL1D, LOGL_INFO, "Message L1 header != Message Data\n"); + + /* Prepend 16-bit length before sending */ + len = msgb_push(msg, L1CTL_MSG_LEN_FIELD); + osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len); + + if (osmo_wqueue_enqueue(&client->wq, msg) != 0) { + LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n"); + msgb_free(msg); + return -EIO; + } + + return 0; +} + +void l1ctl_client_conn_close(struct l1ctl_client *client) +{ + struct l1ctl_server *server = client->server; + + LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n"); + + if (server->cfg->conn_close_cb != NULL) + server->cfg->conn_close_cb(client); + + /* Close connection socket */ + osmo_fd_unregister(&client->wq.bfd); + close(client->wq.bfd.fd); + client->wq.bfd.fd = -1; + + /* Clear pending messages */ + osmo_wqueue_clear(&client->wq); + + client->server->num_clients--; + llist_del(&client->list); + talloc_free(client); + + /* If this was the last client, reset the client IDs generator to 0. + * This way avoid assigning huge unreadable client IDs like 26545. */ + if (llist_empty(&server->clients)) + server->next_client_id = 0; +} + +struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg) +{ + struct l1ctl_server *server; + int rc; + + LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path); + + server = talloc(ctx, struct l1ctl_server); + OSMO_ASSERT(server != NULL); + + *server = (struct l1ctl_server){ + .clients = LLIST_HEAD_INIT(server->clients), + .cfg = cfg, + }; + + /* conn_read_cb shall not be NULL */ + OSMO_ASSERT(cfg->conn_read_cb != NULL); + + /* Bind connection handler */ + osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0); + + rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0, cfg->sock_path, OSMO_SOCK_F_BIND); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n", strerror(errno)); + talloc_free(server); + return NULL; + } + + return server; +} + +void l1ctl_server_free(struct l1ctl_server *server) +{ + LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n"); + + /* Close all client connections */ + while (!llist_empty(&server->clients)) { + struct l1ctl_client *client = llist_entry(server->clients.next, struct l1ctl_client, list); + l1ctl_client_conn_close(client); + } + + /* Unbind listening socket */ + if (server->ofd.fd != -1) { + osmo_fd_unregister(&server->ofd); + close(server->ofd.fd); + server->ofd.fd = -1; + } + + talloc_free(server); +} diff --git a/Transceiver52M/ms/l1ctl_server_cb.cpp b/Transceiver52M/ms/l1ctl_server_cb.cpp new file mode 100644 index 0000000..42f64ac --- /dev/null +++ b/Transceiver52M/ms/l1ctl_server_cb.cpp @@ -0,0 +1,71 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +extern "C" { +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/trxcon/l1ctl_server.h> +} +#include "ms_trxcon_if.h" + +static struct l1ctl_server_cfg server_cfg; +static struct l1ctl_server *server = NULL; + +static int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg) +{ + struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv; + + return trxcon_l1ctl_receive(trxcon, msg); +} + +static void l1ctl_conn_accept_cb(struct l1ctl_client *l1c) +{ + l1c->log_prefix = talloc_strdup(l1c, g_trxcon->log_prefix); + l1c->priv = g_trxcon; + g_trxcon->l2if = l1c; +} + +static void l1ctl_conn_close_cb(struct l1ctl_client *l1c) +{ + struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv; + + if (trxcon == NULL || trxcon->fi == NULL) + return; + + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_L2IF_FAILURE, NULL); +} + +bool trxc_l1ctl_init(void *tallctx) +{ + /* Start the L1CTL server */ + server_cfg = (struct l1ctl_server_cfg){ + /* TODO: make path configurable */ + .sock_path = "/tmp/osmocom_l2", .num_clients_max = 1, + .conn_read_cb = &l1ctl_rx_cb, .conn_accept_cb = &l1ctl_conn_accept_cb, + .conn_close_cb = &l1ctl_conn_close_cb, + }; + + server = l1ctl_server_alloc(tallctx, &server_cfg); + if (server == NULL) { + return false; + } + return true; +} diff --git a/Transceiver52M/ms/logging.c b/Transceiver52M/ms/logging.c new file mode 100644 index 0000000..af397e0 --- /dev/null +++ b/Transceiver52M/ms/logging.c @@ -0,0 +1,98 @@ +/* + * OsmocomBB <-> SDR connection bridge + * + * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> + * + * All Rights Reserved + * + * 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 2 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. + * + */ + +#include <osmocom/core/application.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> + +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/logging.h> + +static struct log_info_cat trxcon_log_info_cat[] = { + [DAPP] = { + .name = "DAPP", + .description = "Application", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DL1C] = { + .name = "DL1C", + .description = "Layer 1 control interface", + .color = "\033[1;31m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DL1D] = { + .name = "DL1D", + .description = "Layer 1 data", + .color = "\033[1;31m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DTRXC] = { + .name = "DTRXC", + .description = "Transceiver control interface", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DTRXD] = { + .name = "DTRXD", + .description = "Transceiver data interface", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSCH] = { + .name = "DSCH", + .description = "Scheduler management", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSCHD] = { + .name = "DSCHD", + .description = "Scheduler data", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DGPRS] = { + .name = "DGPRS", + .description = "L1 GPRS (MAC layer)", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, +}; + +static const struct log_info trxcon_log_info = { + .cat = trxcon_log_info_cat, + .num_cat = ARRAY_SIZE(trxcon_log_info_cat), +}; + +static const int trxcon_log_cfg[] = { + [TRXCON_LOGC_FSM] = DAPP, + [TRXCON_LOGC_L1C] = DL1C, + [TRXCON_LOGC_L1D] = DL1D, + [TRXCON_LOGC_SCHC] = DSCH, + [TRXCON_LOGC_SCHD] = DSCHD, + [TRXCON_LOGC_GPRS] = DGPRS, +}; + +void trxc_log_init(void *tallctx) +{ + osmo_init_logging2(tallctx, &trxcon_log_info); + log_target_file_switch_to_wqueue(osmo_stderr_target); + + trxcon_set_log_cfg(&trxcon_log_cfg[0], ARRAY_SIZE(trxcon_log_cfg)); +} diff --git a/Transceiver52M/ms/ms.cpp b/Transceiver52M/ms/ms.cpp new file mode 100644 index 0000000..4ae8668 --- /dev/null +++ b/Transceiver52M/ms/ms.cpp @@ -0,0 +1,160 @@ + +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include "GSMCommon.h" +#include <atomic> +#include <cassert> +#include <complex> +#include <iostream> +#include <cstdlib> +#include <cstdio> +#include <thread> +#include <fstream> + +#include "ms.h" + +extern "C" { +#include "sch.h" +} + +#include "threadsched.h" + +dummylog ms_trx::dummy_log; + +#ifdef DBGXX +const int offsetrange = 200; +const int offset_start = -15; +static int offset_ctr = 0; +#endif + +template <> +std::atomic<bool> ms_trx::base::stop_lower_threads_flag(false); + +int ms_trx::init_dev_and_streams() +{ + int status = 0; + status = init_device(rx_bh(), tx_bh()); + if (status < 0) { + std::cerr << "failed to init dev!" << std::endl; + return -1; + } + return status; +} + +bh_fn_t ms_trx::rx_bh() +{ + return [this](dev_buf_t *rcd) -> int { + if (this->search_for_sch(rcd) == SCH_STATE::FOUND) + this->grab_bursts(rcd); + return 0; + }; +} + +bh_fn_t ms_trx::tx_bh() +{ + return [this](dev_buf_t *rcd) -> int { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" + auto y = this; +#pragma GCC diagnostic pop + /* nothing to do here */ + return 0; + }; +} + +void ms_trx::start_lower_ms() +{ + if (stop_lower_threads_flag) + return; + auto fn = get_rx_burst_handler_fn(rx_bh()); + lower_rx_task = spawn_worker_thread(sched_params::thread_names::RXRUN, fn, this); + + usleep(1000); + auto fn2 = get_tx_burst_handler_fn(tx_bh()); + lower_tx_task = spawn_worker_thread(sched_params::thread_names::TXRUN, fn2, this); + + actually_enable_streams(); +} + +void ms_trx::set_upper_ready(bool is_ready) +{ + upper_is_ready = is_ready; +} + +void ms_trx::stop_threads() +{ + std::cerr << "killing threads..." << std::endl; + stop_lower_threads_flag = true; + close_device(); + std::cerr << "dev closed..." << std::endl; + pthread_join(lower_rx_task, nullptr); + std::cerr << "L rx dead..." << std::endl; + pthread_join(lower_tx_task, nullptr); + std::cerr << "L tx dead..." << std::endl; +} + +void ms_trx::submit_burst(blade_sample_type *buffer, int len, GSM::Time target) +{ + int64_t now_ts; + GSM::Time now_time; + target.incTN(3); // ul dl offset + int target_fn = target.FN(); + int target_tn = target.TN(); + timekeeper.get_both(&now_time, &now_ts); + + auto diff_fn = GSM::FNDelta(target_fn, now_time.FN()); + int diff_tn = (target_tn - (int)now_time.TN()) % 8; + auto tosend = GSM::Time(diff_fn, 0); + + if (diff_tn > 0) + tosend.incTN(diff_tn); + else + tosend.decTN(-diff_tn); + + // in theory fn equal and tn+3 equal is also a problem... + if (diff_fn < 0 || (diff_fn == 0 && (target_tn-now_time.TN() < 3))) { + std::cerr << "## TX too late?! fn DIFF:" << diff_fn << " tn LOCAL: " << now_time.TN() + << " tn OTHER: " << target_tn << std::endl; + return; + } + + int64_t send_ts = now_ts + tosend.FN() * 8 * ONE_TS_BURST_LEN + tosend.TN() * ONE_TS_BURST_LEN - timing_advance; +#ifdef DBGXX + auto check = now_time + tosend; + std::cerr << "## fn DIFF: " << diff_fn << " ## tn DIFF: " << diff_tn << " tn LOCAL/OTHER: " << now_time.TN() + << "/" << target_tn << " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":" << tosend.TN() + << " check: " << check.FN() << ":" << check.TN() << " target: " << target.FN() << ":" << target.TN() + << " ts now: " << now_ts << " target ts:" << send_ts << std::endl; +#endif +#if 0 + auto check = now_time + tosend; + unsigned int pad = 4 * 4; + blade_sample_type buf2[len + pad]; + std::fill(buf2, buf2 + pad, 0); + memcpy(&buf2[pad], buffer, len * sizeof(blade_sample_type)); + + assert(target.FN() == check.FN()); + assert(target.TN() == check.TN()); + submit_burst_ts(buf2, len + pad, send_ts - pad); +#else + submit_burst_ts(buffer, len, send_ts); +#endif +} diff --git a/Transceiver52M/ms/ms.h b/Transceiver52M/ms/ms.h new file mode 100644 index 0000000..2ad78de --- /dev/null +++ b/Transceiver52M/ms/ms.h @@ -0,0 +1,301 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <atomic> +#include <cassert> +#include <complex> +#include <cstdint> +#include <mutex> +#include <iostream> +// #include <thread> + +#if defined(BUILDBLADE) +#include "bladerf_specific.h" +#define BASET blade_hw<ms_trx> +#elif defined(BUILDUHD) +#include "uhd_specific.h" +#define BASET uhd_hw<ms_trx> +#else +#error wat? no device.. +#endif + +#include "Complex.h" +#include "GSMCommon.h" +#include "itrq.h" +#include "threadpool.h" +#include "threadsched.h" + +const unsigned int ONE_TS_BURST_LEN = (3 + 58 + 26 + 58 + 3 + 8.25) * 4 /*sps*/; +const unsigned int SCH_LEN_SPS = (ONE_TS_BURST_LEN * 8 /*ts*/ * 12 /*frames*/); + +template <typename T> +void clamp_array(T *start2, unsigned int len, T max) +{ + for (unsigned int i = 0; i < len; i++) { + const T t1 = start2[i] < -max ? -max : start2[i]; + const T t2 = t1 > max ? max : t1; + start2[i] = t2; + } +} + +namespace cvt_internal +{ + +template <typename SRC_T, typename ST> +void convert_and_scale_i(float *dst, const SRC_T *src, unsigned int src_len, ST scale) +{ + for (unsigned int i = 0; i < src_len; i++) + dst[i] = static_cast<float>(src[i]) * scale; +} + +template <typename DST_T, typename ST> +void convert_and_scale_i(DST_T *dst, const float *src, unsigned int src_len, ST scale) +{ + for (unsigned int i = 0; i < src_len; i++) + dst[i] = static_cast<DST_T>(src[i] * scale); +} + +template <typename ST> +void convert_and_scale_i(float *dst, const float *src, unsigned int src_len, ST scale) +{ + for (unsigned int i = 0; i < src_len; i++) + dst[i] = src[i] * scale; +} + +template <typename T> +struct is_complex : std::false_type { + using baset = T; + static const unsigned int len_mul = 1; +}; + +template <typename T> +struct is_complex<std::complex<T>> : std::true_type { + using baset = typename std::complex<T>::value_type; + static const unsigned int len_mul = 2; +}; + +template <typename T> +struct is_complex<Complex<T>> : std::true_type { + using baset = typename Complex<T>::value_type; + static const unsigned int len_mul = 2; +}; + +} // namespace cvt_internal + +template <typename DST_T, typename SRC_T, typename ST> +void convert_and_scale(DST_T *dst, const SRC_T *src, unsigned int src_len, ST scale) +{ + using vd = typename cvt_internal::is_complex<DST_T>::baset; + using vs = typename cvt_internal::is_complex<SRC_T>::baset; + return cvt_internal::convert_and_scale_i((vd *)dst, (vs *)src, src_len, scale); +} + +template <typename array_t> +float normed_abs_sum(array_t *src, int len) +{ + using vd = typename cvt_internal::is_complex<array_t>::baset; + auto len_mul = cvt_internal::is_complex<array_t>::len_mul; + auto ptr = reinterpret_cast<const vd *>(src); + float sum = 0; + for (unsigned int i = 0; i < len * len_mul; i++) + sum += std::abs(ptr[i]); + sum /= len * len_mul; + return sum; +} + +struct one_burst { + one_burst() + { + } + GSM::Time gsmts; + union { + blade_sample_type burst[ONE_TS_BURST_LEN]; + char sch_bits[148]; + }; +}; + +using rx_queue_t = spsc_cond_timeout<4, one_burst, true, false>; + +enum class SCH_STATE { SEARCHING, FOUND }; + +class dummylog : private std::streambuf { + std::ostream null_stream; + + public: + dummylog() : null_stream(this){}; + ~dummylog() override{}; + std::ostream &operator()() + { + return null_stream; + } + int overflow(int c) override + { + return c; + } +}; + +// keeps relationship between gsm time and (continuously adjusted) ts +class time_keeper { + GSM::Time global_time_keeper; + int64_t global_ts_keeper; + std::mutex m; + + public: + time_keeper() : global_time_keeper(0), global_ts_keeper(0) + { + } + + void set(GSM::Time t, int64_t ts) + { + std::lock_guard<std::mutex> g(m); + global_time_keeper = t; + global_ts_keeper = ts; + } + void inc_both() + { + std::lock_guard<std::mutex> g(m); + global_time_keeper.incTN(1); + global_ts_keeper += ONE_TS_BURST_LEN; + } + void inc_and_update(int64_t new_ts) + { + std::lock_guard<std::mutex> g(m); + global_time_keeper.incTN(1); + global_ts_keeper = new_ts; + // std::cerr << "u " << new_ts << std::endl; + } + void inc_and_update_safe(int64_t new_ts) + { + std::lock_guard<std::mutex> g(m); + auto diff = new_ts - global_ts_keeper; + assert(diff < 1.5 * ONE_TS_BURST_LEN); + assert(diff > 0.5 * ONE_TS_BURST_LEN); + global_time_keeper.incTN(1); + global_ts_keeper = new_ts; + // std::cerr << "s " << new_ts << std::endl; + } + void dec_by_one() + { + std::lock_guard<std::mutex> g(m); + global_time_keeper.decTN(1); + global_ts_keeper -= ONE_TS_BURST_LEN; + } + auto get_ts() + { + std::lock_guard<std::mutex> g(m); + return global_ts_keeper; + } + auto gsmtime() + { + std::lock_guard<std::mutex> g(m); + return global_time_keeper; + } + void get_both(GSM::Time *t, int64_t *ts) + { + std::lock_guard<std::mutex> g(m); + *t = global_time_keeper; + *ts = global_ts_keeper; + } +}; + +using ts_hitter_q_t = spsc_cond<64, GSM::Time, true, false>; + +// used to globally initialize the sched/hw information +struct sched_hw_info { + int hw_cpus; + sched_params::target hw_target; + + sched_hw_info() + { + hw_cpus = std::thread::hardware_concurrency(); + hw_target = hw_cpus > 4 ? sched_params::target::ODROID : sched_params::target::PI4; + set_sched_target(hw_target); + std::cerr << "scheduling for: " << (hw_cpus > 4 ? "odroid" : "pi4") << std::endl; + } +}; + +struct ms_trx : public BASET, public sched_hw_info { + using base = BASET; + static dummylog dummy_log; + unsigned int mTSC; + unsigned int mBSIC; + int timing_advance; + bool do_auto_gain; + + pthread_t lower_rx_task; + pthread_t lower_tx_task; + + // provides bursts to upper rx thread + rx_queue_t rxqueue; + + blade_sample_type *first_sch_buf; + blade_sample_type *burst_copy_buffer; + + uint64_t first_sch_buf_rcv_ts; + std::atomic<bool> rcv_done; + std::atomic<bool> sch_thread_done; + + int64_t temp_ts_corr_offset = 0; + int64_t first_sch_ts_start = -1; + + time_keeper timekeeper; + single_thread_pool worker_thread; // uses base class sched target hw info + + void start_lower_ms(); + std::atomic<bool> upper_is_ready; + void set_upper_ready(bool is_ready); + + bool handle_sch_or_nb(); + bool handle_sch(bool first = false); + bool decode_sch(char *bits, bool update_global_clock); + SCH_STATE search_for_sch(dev_buf_t *rcd); + void grab_bursts(dev_buf_t *rcd); + + int init_dev_and_streams(); + void stop_threads(); + void *rx_cb(ms_trx *t); + void *tx_cb(); + void maybe_update_gain(one_burst &brst); + + ms_trx() + : mTSC(0), mBSIC(0), timing_advance(0), do_auto_gain(false), rxqueue(), + first_sch_buf(new blade_sample_type[SCH_LEN_SPS]), + burst_copy_buffer(new blade_sample_type[ONE_TS_BURST_LEN]), first_sch_buf_rcv_ts(0), + rcv_done{ false }, sch_thread_done{ false }, upper_is_ready(false) + { + } + + virtual ~ms_trx() + { + delete[] burst_copy_buffer; + delete[] first_sch_buf; + } + bh_fn_t rx_bh(); + bh_fn_t tx_bh(); + + void submit_burst(blade_sample_type *buffer, int len, GSM::Time); + void set_ta(int val) + { + assert(val > -127 && val < 128); + timing_advance = val * 4; + } +}; diff --git a/Transceiver52M/ms/ms_rx_lower.cpp b/Transceiver52M/ms/ms_rx_lower.cpp new file mode 100644 index 0000000..d894e96 --- /dev/null +++ b/Transceiver52M/ms/ms_rx_lower.cpp @@ -0,0 +1,420 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "sigProcLib.h" +#include "signalVector.h" +#include <atomic> +#include <cassert> +#include <complex> +#include <iostream> +#include <future> + +#include "ms.h" +#include "grgsm_vitac/grgsm_vitac.h" + +#include "threadpool.h" + +extern "C" { +#include "sch.h" +} + +#ifdef LOG +#undef LOG +#endif + +#if !defined(NODAMNLOG) +#define DBGLG(...) ms_trx::dummy_log() +#else +#define DBGLG(...) std::cerr +#endif + +#if !defined(NODAMNLOG) +#define DBGLG2(...) ms_trx::dummy_log() +#else +#define DBGLG2(...) std::cerr +#endif + +#define PRINT_Q_OVERFLOW + +bool ms_trx::decode_sch(char *bits, bool update_global_clock) +{ + int fn; + struct sch_info sch; + ubit_t info[GSM_SCH_INFO_LEN]; + sbit_t data[GSM_SCH_CODED_LEN]; + + memcpy(&data[0], &bits[3], 39); + memcpy(&data[39], &bits[106], 39); + + if (!gsm_sch_decode(info, data)) { + gsm_sch_parse(info, &sch); + + if (update_global_clock) { + DBGLG() << "SCH : Decoded values" << std::endl; + DBGLG() << " BSIC: " << sch.bsic << std::endl; + DBGLG() << " TSC: " << (sch.bsic & 0x7) << std::endl; + DBGLG() << " T1 : " << sch.t1 << std::endl; + DBGLG() << " T2 : " << sch.t2 << std::endl; + DBGLG() << " T3p : " << sch.t3p << std::endl; + DBGLG() << " FN : " << gsm_sch_to_fn(&sch) << std::endl; + } + + fn = gsm_sch_to_fn(&sch); + if (fn < 0) { // how? wh? + DBGLG() << "SCH : Failed to convert FN " << std::endl; + return false; + } + + if (update_global_clock) { + mBSIC = sch.bsic; + mTSC = sch.bsic & 0x7; + timekeeper.set(fn, 0); + // global_time_keeper.FN(fn); + // global_time_keeper.TN(0); + } + + return true; + } + return false; +} + +void ms_trx::maybe_update_gain(one_burst &brst) +{ + static_assert((sizeof(brst.burst) / sizeof(brst.burst[0])) == ONE_TS_BURST_LEN, "wtf, buffer size mismatch?"); + const int avgburst_num = 8 * 20; // ~ 50*4.5ms = 90ms? + static_assert(avgburst_num * 577 > (50 * 1000), "can't update faster then blade wait time?"); + const unsigned int rx_max_cutoff = (rxFullScale * 2) / 3; + static int gain_check = 0; + static float runmean = 0; + float sum = normed_abs_sum(&brst.burst[0], ONE_TS_BURST_LEN); + runmean = gain_check ? (runmean * (gain_check + 2) - 1 + sum) / (gain_check + 2) : sum; + + if (gain_check == avgburst_num - 1) { + DBGLG2() << "\x1B[32m #RXG \033[0m" << rxgain << " " << runmean << " " << sum << std::endl; + auto gainoffset = runmean < (rxFullScale / 4 ? 4 : 2); + gainoffset = runmean < (rxFullScale / 2 ? 2 : 1); + float newgain = runmean < rx_max_cutoff ? rxgain + gainoffset : rxgain - gainoffset; + // FIXME: gian cutoff + if (newgain != rxgain && newgain <= 60) { + auto gain_fun = [this, newgain] { setRxGain(newgain); }; + worker_thread.add_task(gain_fun); + } + + runmean = 0; + } + gain_check = (gain_check + 1) % avgburst_num; +} + +static char sch_demod_bits[148]; + +bool ms_trx::handle_sch_or_nb() +{ + one_burst brst; + const auto current_gsm_time = timekeeper.gsmtime(); + const auto is_sch = gsm_sch_check_ts(current_gsm_time.TN(), current_gsm_time.FN()); + + //either pass burst to upper layer for demod, OR pass demodded SCH to upper layer so we don't waste time processing it twice + brst.gsmts = current_gsm_time; + + if (!is_sch) { + memcpy(brst.burst, burst_copy_buffer, sizeof(blade_sample_type) * ONE_TS_BURST_LEN); + } else { + handle_sch(false); + memcpy(brst.sch_bits, sch_demod_bits, sizeof(sch_demod_bits)); + } + + while (upper_is_ready && !rxqueue.spsc_push(&brst)) + ; + + if (do_auto_gain) + maybe_update_gain(brst); + + return false; +} + +static float sch_acq_buffer[SCH_LEN_SPS * 2]; + +bool ms_trx::handle_sch(bool is_first_sch_acq) +{ + auto current_gsm_time = timekeeper.gsmtime(); + const auto buf_len = is_first_sch_acq ? SCH_LEN_SPS : ONE_TS_BURST_LEN; + const auto which_in_buffer = is_first_sch_acq ? first_sch_buf : burst_copy_buffer; + memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer)); +#if 1 + const auto which_out_buffer = is_first_sch_acq ? sch_acq_buffer : &sch_acq_buffer[40 * 2]; + const auto ss = reinterpret_cast<std::complex<float> *>(which_out_buffer); + std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR]; + int start; + convert_and_scale(which_out_buffer, which_in_buffer, buf_len * 2, 1.f / float(rxFullScale)); + if (is_first_sch_acq) { + float max_corr = 0; + start = get_sch_buffer_chan_imp_resp(ss, &channel_imp_resp[0], buf_len, &max_corr); + } else { + start = get_sch_chan_imp_resp(ss, &channel_imp_resp[0]); + start = start < 39 ? start : 39; + start = start > -39 ? start : -39; + } + detect_burst_nb(&ss[start], &channel_imp_resp[0], 0, sch_demod_bits); + + auto sch_decode_success = decode_sch(sch_demod_bits, is_first_sch_acq); +#if 0 + auto burst = new signalVector(buf_len, 50); + const auto corr_type = is_first_sch_acq ? sch_detect_type::SCH_DETECT_BUFFER : sch_detect_type::SCH_DETECT_FULL; + struct estim_burst_params ebp; + + // scale like uhd, +-2k -> +-32k + convert_and_scale(burst->begin(), which_in_buffer, buf_len * 2, SAMPLE_SCALE_FACTOR); + + auto rv = detectSCHBurst(*burst, 4, 4, corr_type, &ebp); + + int howmuchdelay = ebp.toa * 4; + std::cerr << "ooffs: " << howmuchdelay << " " << std::endl; + std::cerr << "voffs: " << start << " " << sch_decode_success << std::endl; +#endif + if (sch_decode_success) { + const auto ts_offset_symb = 4; + if (is_first_sch_acq) { + // update ts to first sample in sch buffer, to allow delay calc for current ts + first_sch_ts_start = first_sch_buf_rcv_ts + start - (ts_offset_symb * 4) - 1; + } else if (abs(start) > 1) { + // continuous sch tracking, only update if off too much + temp_ts_corr_offset += -start; + std::cerr << "offs: " << start << " " << temp_ts_corr_offset << std::endl; + } + + return true; + } else { + DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << start << " " << current_gsm_time.FN() + << ":" << current_gsm_time.TN() << std::endl; + } +#else + const auto ts_offset_symb = 4; + auto burst = new signalVector(buf_len, 50); + const auto corr_type = is_first_sch_acq ? sch_detect_type::SCH_DETECT_BUFFER : sch_detect_type::SCH_DETECT_FULL; + struct estim_burst_params ebp; + + // scale like uhd, +-2k -> +-32k + convert_and_scale(burst->begin(), which_in_buffer, buf_len * 2, SAMPLE_SCALE_FACTOR); + + auto rv = detectSCHBurst(*burst, 4, 4, corr_type, &ebp); + + int howmuchdelay = ebp.toa * 4; + + if (!rv) { + delete burst; + DBGLG() << "SCH : \x1B[31m detect fail \033[0m NOOOOOOOOOOOOOOOOOO toa:" << ebp.toa << " " + << current_gsm_time.FN() << ":" << current_gsm_time.TN() << std::endl; + return false; + } + + SoftVector *bits; + if (is_first_sch_acq) { + // can't be legit with a buf size spanning _at least_ one SCH but delay that implies partial sch burst + if (howmuchdelay < 0 || (buf_len - howmuchdelay) < ONE_TS_BURST_LEN) { + delete burst; + return false; + } + + struct estim_burst_params ebp2; + // auto sch_chunk = new signalVector(ONE_TS_BURST_LEN, 50); + // auto sch_chunk_start = sch_chunk->begin(); + // memcpy(sch_chunk_start, sch_buf_f.data() + howmuchdelay, sizeof(std::complex<float>) * ONE_TS_BURST_LEN); + + auto delay = delayVector(burst, NULL, -howmuchdelay); + + scaleVector(*delay, (complex)1.0 / ebp.amp); + + auto rv2 = detectSCHBurst(*delay, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp2); + DBGLG() << "FIRST SCH : " << (rv2 ? "yes " : " ") << "Timing offset " << ebp2.toa << " symbols" + << std::endl; + + bits = demodAnyBurst(*delay, SCH, 4, &ebp2); + delete delay; + } else { + bits = demodAnyBurst(*burst, SCH, 4, &ebp); + } + + delete burst; + + // clamp to +-1.5 because +-127 softbits scaled by 64 after -0.5 can be at most +-1.5 + clamp_array(bits->begin(), 148, 1.5f); + + float_to_sbit(&bits->begin()[0], (signed char *)&sch_demod_bits[0], 62, 148); + // float_to_sbit(&bits->begin()[106], &data[39], 62, 39); + + if (decode_sch((char *)sch_demod_bits, is_first_sch_acq)) { + auto current_gsm_time_updated = timekeeper.gsmtime(); + if (is_first_sch_acq) { + // update ts to first sample in sch buffer, to allow delay calc for current ts + first_sch_ts_start = first_sch_buf_rcv_ts + howmuchdelay - (ts_offset_symb * 4); + } else { + // continuous sch tracking, only update if off too much + auto diff = [](float x, float y) { return x > y ? x - y : y - x; }; + + auto d = diff(ebp.toa, ts_offset_symb); + if (abs(d) > 0.3) { + if (ebp.toa < ts_offset_symb) + ebp.toa = d; + else + ebp.toa = -d; + temp_ts_corr_offset += ebp.toa * 4; + + DBGLG() << "offs: " << ebp.toa << " " << temp_ts_corr_offset << std::endl; + } + } + + auto a = gsm_sch_check_fn(current_gsm_time_updated.FN() - 1); + auto b = gsm_sch_check_fn(current_gsm_time_updated.FN()); + auto c = gsm_sch_check_fn(current_gsm_time_updated.FN() + 1); + DBGLG() << "L SCH : Timing offset " << rv << " " << ebp.toa << " " << a << b << c << "fn " + << current_gsm_time_updated.FN() << ":" << current_gsm_time_updated.TN() << std::endl; + + delete bits; + return true; + } else { + DBGLG2() << "L SCH : \x1B[31m decode fail \033[0m @ toa:" << ebp.toa << " " << current_gsm_time.FN() + << ":" << current_gsm_time.TN() << std::endl; + } + + delete bits; +#endif + return false; +} + +/* +accumulates a full big buffer consisting of 8*12 timeslots, then: +either +1) adjusts gain if necessary and starts over +2) searches and finds SCH and is done +*/ +SCH_STATE ms_trx::search_for_sch(dev_buf_t *rcd) +{ + static unsigned int sch_pos = 0; + auto to_copy = SCH_LEN_SPS - sch_pos; + + if (sch_thread_done) + return SCH_STATE::FOUND; + + if (rcv_done) + return SCH_STATE::SEARCHING; + + if (sch_pos == 0) // keep first ts for time delta calc + first_sch_buf_rcv_ts = rcd->get_first_ts(); + + if (to_copy) { + auto spsmax = rcd->actual_samples_per_buffer(); + if (to_copy > (unsigned int)spsmax) + sch_pos += rcd->readall(first_sch_buf + sch_pos); + else + sch_pos += rcd->read_n(first_sch_buf + sch_pos, 0, to_copy); + } else { // (!to_copy) + sch_pos = 0; + rcv_done = true; + auto sch_search_fun = [this] { + const auto target_val = rxFullScale / 8; + float sum = normed_abs_sum(first_sch_buf, SCH_LEN_SPS); + + //FIXME: arbitrary value, gain cutoff + if (sum > target_val || rxgain >= 60) // enough ? + sch_thread_done = this->handle_sch(true); + else { + std::cerr << "\x1B[32m #RXG \033[0m gain " << rxgain << " -> " << rxgain + 4 + << " sample avg:" << sum << " target: >=" << target_val << std::endl; + setRxGain(rxgain + 4); + } + + if (!sch_thread_done) + rcv_done = false; // retry! + }; + worker_thread.add_task(sch_search_fun); + } + return SCH_STATE::SEARCHING; +} + +void ms_trx::grab_bursts(dev_buf_t *rcd) +{ + // partial burst samples read from the last buffer + static int partial_rdofs = 0; + static bool first_call = true; + int to_skip = 0; + + // round up to next burst by calculating the time between sch detection and now + if (first_call) { + const auto next_burst_start = rcd->get_first_ts() - first_sch_ts_start; + const auto fullts = next_burst_start / ONE_TS_BURST_LEN; + const auto fracts = next_burst_start % ONE_TS_BURST_LEN; + to_skip = ONE_TS_BURST_LEN - fracts; + + for (unsigned int i = 0; i < fullts; i++) + timekeeper.inc_and_update(first_sch_ts_start + i * ONE_TS_BURST_LEN); + + if (fracts) + timekeeper.inc_both(); + // timekeeper.inc_and_update(first_sch_ts_start + 1 * ONE_TS_BURST_LEN); + + timekeeper.dec_by_one(); // oops, off by one? + + timekeeper.set(timekeeper.gsmtime(), rcd->get_first_ts() - ONE_TS_BURST_LEN + to_skip); + + DBGLG() << "this ts: " << rcd->get_first_ts() << " diff full TN: " << fullts << " frac TN: " << fracts + << " GSM now: " << timekeeper.gsmtime().FN() << ":" << timekeeper.gsmtime().TN() << " is sch? " + << gsm_sch_check_fn(timekeeper.gsmtime().FN()) << std::endl; + first_call = false; + } + + if (partial_rdofs) { + auto first_remaining = ONE_TS_BURST_LEN - partial_rdofs; + auto rd = rcd->read_n(burst_copy_buffer + partial_rdofs, 0, first_remaining); + if (rd != (int)first_remaining) { + partial_rdofs += rd; + return; + } + + timekeeper.inc_and_update_safe(rcd->get_first_ts() - partial_rdofs); + handle_sch_or_nb(); + to_skip = first_remaining; + } + + // apply sample rate slippage compensation + to_skip -= temp_ts_corr_offset; + + // FIXME: happens rarely, read_n start -1 blows up + // this is fine: will just be corrected one buffer later + if (to_skip < 0) + to_skip = 0; + else + temp_ts_corr_offset = 0; + + const auto left_after_burst = rcd->actual_samples_per_buffer() - to_skip; + + const int full = left_after_burst / ONE_TS_BURST_LEN; + const int frac = left_after_burst % ONE_TS_BURST_LEN; + + for (int i = 0; i < full; i++) { + rcd->read_n(burst_copy_buffer, to_skip + i * ONE_TS_BURST_LEN, ONE_TS_BURST_LEN); + timekeeper.inc_and_update_safe(rcd->get_first_ts() + to_skip + i * ONE_TS_BURST_LEN); + handle_sch_or_nb(); + } + + if (frac) + rcd->read_n(burst_copy_buffer, to_skip + full * ONE_TS_BURST_LEN, frac); + partial_rdofs = frac; +} diff --git a/Transceiver52M/ms/ms_trxcon_if.cpp b/Transceiver52M/ms/ms_trxcon_if.cpp new file mode 100644 index 0000000..7de9710 --- /dev/null +++ b/Transceiver52M/ms/ms_trxcon_if.cpp @@ -0,0 +1,78 @@ +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <atomic> +#include "ms_trxcon_if.h" +extern "C" { +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/l1ctl_server.h> +#include <osmocom/core/panic.h> +} + +extern tx_queue_t txq; +extern cmd_queue_t cmdq_to_phy; +extern cmdr_queue_t cmdq_from_phy; +extern std::atomic<bool> g_exit_flag; +// trxcon C call(back) if +extern "C" { +int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon_phyif_burst_req *br) +{ + if (br->burst_len == 0) // dummy/nope + return 0; + OSMO_ASSERT(br->burst != 0); + + internal_q_tx_buf b(br); + if (!g_exit_flag) + txq.spsc_push(&b); + return 0; +} + +int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon_phyif_cmd *cmd) +{ +#ifdef TXDEBUG + DBGLG() << "TOP C: " << cmd2str(cmd->type) << std::endl; +#endif + if (!g_exit_flag) + cmdq_to_phy.spsc_push(cmd); + // q for resp polling happens in main loop + return 0; +} + +void trxcon_phyif_close(void *phyif) +{ +} + +void trxcon_l1ctl_close(struct trxcon_inst *trxcon) +{ + /* Avoid use-after-free: both *fi and *trxcon are children of + * the L2IF (L1CTL connection), so we need to re-parent *fi + * to NULL before calling l1ctl_client_conn_close(). */ + talloc_steal(NULL, trxcon->fi); + l1ctl_client_conn_close((struct l1ctl_client *)trxcon->l2if); +} + +int trxcon_l1ctl_send(struct trxcon_inst *trxcon, struct msgb *msg) +{ + struct l1ctl_client *l1c = (struct l1ctl_client *)trxcon->l2if; + + return l1ctl_client_send(l1c, msg); +} +} diff --git a/Transceiver52M/ms/ms_trxcon_if.h b/Transceiver52M/ms/ms_trxcon_if.h new file mode 100644 index 0000000..0928d40 --- /dev/null +++ b/Transceiver52M/ms/ms_trxcon_if.h @@ -0,0 +1,42 @@ +#pragma once +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "ms.h" +extern "C" { +#include <osmocom/bb/trxcon/phyif.h> +} + +extern struct trxcon_inst *g_trxcon; +struct internal_q_tx_buf { + trxcon_phyif_burst_req r; + uint8_t buf[148]; + internal_q_tx_buf() = default; + internal_q_tx_buf(const internal_q_tx_buf &) = delete; + internal_q_tx_buf &operator=(const internal_q_tx_buf &) = default; + internal_q_tx_buf(const struct trxcon_phyif_burst_req *br) : r(*br) + { + memcpy(buf, (void *)br->burst, br->burst_len); + } +}; +using tx_queue_t = spsc_cond<8 * 1, internal_q_tx_buf, true, false>; +using cmd_queue_t = spsc_cond_timeout<8 * 1, trxcon_phyif_cmd, true, false>; +using cmdr_queue_t = spsc_cond<8 * 1, trxcon_phyif_rsp, false, false>; diff --git a/Transceiver52M/ms/ms_upper.cpp b/Transceiver52M/ms/ms_upper.cpp new file mode 100644 index 0000000..2e8bc11 --- /dev/null +++ b/Transceiver52M/ms/ms_upper.cpp @@ -0,0 +1,512 @@ +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <csignal> +#include "sigProcLib.h" +#include "ms.h" +#include <signalVector.h> +#include <radioVector.h> +#include <radioInterface.h> +#include <grgsm_vitac/grgsm_vitac.h> + +// #define TXDEBUG + +extern "C" { + +#include "sch.h" +#include "convolve.h" +#include "convert.h" + +#include <osmocom/core/application.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmocom/bb/trxcon/trxcon.h> +#include <osmocom/bb/trxcon/trxcon_fsm.h> +#include <osmocom/bb/trxcon/l1ctl_server.h> + +extern void trxc_log_init(void *tallctx); +#ifdef LSANDEBUG +void __lsan_do_recoverable_leak_check(); +#endif +} + +#include "ms_trxcon_if.h" +#include "ms_upper.h" +#include "threadsched.h" + +extern bool trxc_l1ctl_init(void *tallctx); +struct trxcon_inst *g_trxcon; +tx_queue_t txq; +cmd_queue_t cmdq_to_phy; +cmdr_queue_t cmdq_from_phy; + +#ifdef LOG +#undef LOG +#define LOG(...) upper_trx::dummy_log() +#endif + +#define DBGLG(...) upper_trx::dummy_log() + +std::atomic<bool> g_exit_flag; + +void upper_trx::stop_upper_threads() +{ + g_exit_flag = true; + + pthread_join(thr_control, NULL); + pthread_join(thr_tx, NULL); +} + +void upper_trx::start_threads() +{ + DBGLG(...) << "spawning threads.." << std::endl; + + thr_control = spawn_worker_thread( + sched_params::thread_names::U_CTL, + [](void *args) -> void * { + upper_trx *t = reinterpret_cast<upper_trx *>(args); +#ifdef TXDEBUG + struct sched_param param; + int policy; + pthread_getschedparam(pthread_self(), &policy, ¶m); + printf("ID: %lu, CPU: %d policy = %d priority = %d\n", pthread_self(), sched_getcpu(), policy, + param.sched_priority); +#endif + std::cerr << "started U control!" << std::endl; + while (!g_exit_flag) { + t->driveControl(); + } + std::cerr << "exit U control!" << std::endl; + + return 0; + }, + this); + thr_tx = spawn_worker_thread( + sched_params::thread_names::U_TX, + [](void *args) -> void * { + upper_trx *t = reinterpret_cast<upper_trx *>(args); +#ifdef TXDEBUG + struct sched_param param; + int policy; + pthread_getschedparam(pthread_self(), &policy, ¶m); + printf("ID: %lu, CPU: %d policy = %d priority = %d\n", pthread_self(), sched_getcpu(), policy, + param.sched_priority); +#endif + std::cerr << "started U tx!" << std::endl; + while (!g_exit_flag) { + t->driveTx(); + } + std::cerr << "exit U tx!" << std::endl; + + return 0; + }, + this); + +#ifdef LSANDEBUG + std::thread([this] { + set_name_aff_sched(sched_params::thread_names::LEAKCHECK); + + while (1) { + std::this_thread::sleep_for(std::chrono::seconds{ 5 }); + __lsan_do_recoverable_leak_check(); + } + }).detach(); +#endif +} + +void upper_trx::main_loop() +{ + set_name_aff_sched(sched_params::thread_names::U_RX); + set_upper_ready(true); + while (!g_exit_flag) { + driveReceiveFIFO(); + osmo_select_main(1); + + trxcon_phyif_rsp r; + if (cmdq_from_phy.spsc_pop(&r)) { + DBGLG() << "HAVE RESP:" << r.type << std::endl; + trxcon_phyif_handle_rsp(g_trxcon, &r); + } + } + set_upper_ready(false); + std::cerr << "exit U rx!" << std::endl; + mOn = false; +} + +// signalvector is owning despite claiming not to, but we can pretend, too.. +static void static_free(void *wData){}; +static void *static_alloc(size_t newSize) +{ + return 0; +}; + +bool upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset) +{ + float pow, avg = 1.0; + const auto zero_pad_len = 40; // give the VA some runway for misaligned bursts + const auto workbuf_size = zero_pad_len + ONE_TS_BURST_LEN + zero_pad_len; + static complex workbuf[workbuf_size]; + + static signalVector sv(workbuf, zero_pad_len, ONE_TS_BURST_LEN, static_alloc, static_free); + one_burst e; + auto ss = reinterpret_cast<std::complex<float> *>(&workbuf[zero_pad_len]); + std::fill(workbuf, workbuf + workbuf_size, 0); + // assert(sv.begin() == &workbuf[40]); + + while (!rxqueue.spsc_pop(&e)) { + rxqueue.spsc_prep_pop(); + } + + wTime = e.gsmts; + + const auto is_sch = gsm_sch_check_ts(wTime.TN(), wTime.FN()); + const auto is_fcch = gsm_fcch_check_ts(wTime.TN(), wTime.FN()); + + trxcon_phyif_rtr_ind i = { static_cast<uint32_t>(wTime.FN()), static_cast<uint8_t>(wTime.TN()) }; + trxcon_phyif_rtr_rsp r = {}; + trxcon_phyif_handle_rtr_ind(g_trxcon, &i, &r); + if (!(r.flags & TRXCON_PHYIF_RTR_F_ACTIVE)) + return false; + + if (is_fcch) { + // return trash + return true; + } + + if (is_sch) { + for (int i = 0; i < 148; i++) + (demodded_softbits)[i] = (e.sch_bits[i]); + RSSI = 10; + timingOffset = 0; + return true; + } + +#if 1 + convert_and_scale(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale)); + + pow = energyDetect(sv, 20 * 4 /*sps*/); + if (pow < -1) { + LOG(ALERT) << "Received empty burst"; + return false; + } + + avg = sqrt(pow); + { + float ncmax; + std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR]; + auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, mTSC); +#ifdef DBGXX + float dcmax; + std::complex<float> chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR]; + auto dummy_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp2[0], &dcmax, TS_DUMMY); + auto is_nb = ncmax > dcmax; + // DBGLG() << " U " << (is_nb ? "NB" : "DB") << "@ o nb: " << normal_burst_start + // << " o db: " << dummy_burst_start << std::endl; +#endif + normal_burst_start = normal_burst_start < 39 ? normal_burst_start : 39; + normal_burst_start = normal_burst_start > -39 ? normal_burst_start : -39; +#ifdef DBGXX + // fprintf(stderr, "%s %d\n", (is_nb ? "N":"D"), burst_time.FN()); + // if (is_nb) +#endif + detect_burst_nb(ss, &chan_imp_resp[0], normal_burst_start, demodded_softbits); +#ifdef DBGXX + // else + // detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin); +#endif + } +#else + + // lower layer sch detection offset, easy to verify by just printing the detected value using both the va+sigproc code. + convert_and_scale(ss + 16, e.burst, ONE_TS_BURST_LEN * 2, 15); + + pow = energyDetect(sv, 20 * 4 /*sps*/); + if (pow < -1) { + LOG(ALERT) << "Received empty burst"; + return false; + } + + avg = sqrt(pow); + + /* Detect normal or RACH bursts */ + CorrType type = CorrType::TSC; + struct estim_burst_params ebp; + auto rc = detectAnyBurst(sv, mTSC, 3, 4, type, 48, &ebp); + if (rc > 0) { + type = (CorrType)rc; + } + + if (rc < 0) { + std::cerr << "UR : \x1B[31m rx fail \033[0m @ toa:" << ebp.toa << " " << e.gsmts.FN() << ":" + << e.gsmts.TN() << std::endl; + return false; + } + SoftVector *bits = demodAnyBurst(sv, type, 4, &ebp); + + SoftVector::const_iterator burstItr = bits->begin(); + // invert and fix to +-127 sbits + for (int ii = 0; ii < 148; ii++) { + demodded_softbits[ii] = *burstItr++ > 0.0f ? -127 : 127; + } + delete bits; + +#endif + RSSI = (int)floor(20.0 * log10(rxFullScale / avg)); + // FIXME: properly handle offset, sch/nb alignment diff? handled by lower anyway... + timingOffset = (int)round(0); + + return true; +} + +void upper_trx::driveReceiveFIFO() +{ + int RSSI; + int TOA; // in 1/256 of a symbol + GSM::Time burstTime; + + if (!mOn) + return; + + if (pullRadioVector(burstTime, RSSI, TOA)) { + trxcon_phyif_burst_ind bi; + bi.fn = burstTime.FN(); + bi.tn = burstTime.TN(); + bi.rssi = RSSI; + bi.toa256 = TOA; + bi.burst = (sbit_t *)demodded_softbits; + bi.burst_len = sizeof(demodded_softbits); + trxcon_phyif_handle_burst_ind(g_trxcon, &bi); + } + + burstTime.incTN(2); + struct trxcon_phyif_rts_ind rts { + static_cast<uint32_t>(burstTime.FN()), static_cast<uint8_t>(burstTime.TN()) + }; + trxcon_phyif_handle_rts_ind(g_trxcon, &rts); +} + +void upper_trx::driveTx() +{ + internal_q_tx_buf e; + static BitVector newBurst(sizeof(e.buf)); + while (!txq.spsc_pop(&e)) { + txq.spsc_prep_pop(); + } + + // ensure our tx cb is tickled and can exit + if (g_exit_flag) { + submit_burst_ts(0, 1337, 1); + return; + } + + internal_q_tx_buf *burst = &e; + +#ifdef TXDEBUG2 + DBGLG() << "got burst!" << burst->r.fn << ":" << burst->ts << " current: " << timekeeper.gsmtime().FN() + << " dff: " << (int64_t)((int64_t)timekeeper.gsmtime().FN() - (int64_t)burst->r.fn) << std::endl; +#endif + + auto currTime = GSM::Time(burst->r.fn, burst->r.tn); + int RSSI = (int)burst->r.pwr; + + BitVector::iterator itr = newBurst.begin(); + auto *bufferItr = burst->buf; + while (itr < newBurst.end()) + *itr++ = *bufferItr++; + + auto txburst = modulateBurst(newBurst, 8 + (currTime.TN() % 4 == 0), 4); + scaleVector(*txburst, txFullScale * pow(10, -RSSI / 10)); + + // float -> int16 + blade_sample_type burst_buf[txburst->size()]; + convert_and_scale(burst_buf, txburst->begin(), txburst->size() * 2, 1); +#ifdef TXDEBUG2 + auto check = signalVector(txburst->size(), 40); + convert_and_scale(check.begin(), burst_buf, txburst->size() * 2, 1); + estim_burst_params ebp; + auto d = detectAnyBurst(check, 2, 4, 4, CorrType::RACH, 40, &ebp); + if (d) + DBGLG() << "RACH D! " << ebp.toa << std::endl; + else + DBGLG() << "RACH NOOOOOOOOOO D! " << ebp.toa << std::endl; + + // memory read --binary --outfile /tmp/mem.bin &burst_buf[0] --count 2500 --force +#endif + submit_burst(burst_buf, txburst->size(), currTime); + delete txburst; +} + +#ifdef TXDEBUG +static const char *cmd2str(trxcon_phyif_cmd_type c) +{ + switch (c) { + case TRXCON_PHYIF_CMDT_RESET: + return "TRXCON_PHYIF_CMDT_RESET"; + case TRXCON_PHYIF_CMDT_POWERON: + return "TRXCON_PHYIF_CMDT_POWERON"; + case TRXCON_PHYIF_CMDT_POWEROFF: + return "TRXCON_PHYIF_CMDT_POWEROFF"; + case TRXCON_PHYIF_CMDT_MEASURE: + return "TRXCON_PHYIF_CMDT_MEASURE"; + case TRXCON_PHYIF_CMDT_SETFREQ_H0: + return "TRXCON_PHYIF_CMDT_SETFREQ_H0"; + case TRXCON_PHYIF_CMDT_SETFREQ_H1: + return "TRXCON_PHYIF_CMDT_SETFREQ_H1"; + case TRXCON_PHYIF_CMDT_SETSLOT: + return "TRXCON_PHYIF_CMDT_SETSLOT"; + case TRXCON_PHYIF_CMDT_SETTA: + return "TRXCON_PHYIF_CMDT_SETTA"; + default: + return "UNKNOWN COMMAND!"; + } +} + +static void print_cmd(trxcon_phyif_cmd_type c) +{ + DBGLG() << "handling " << cmd2str(c) << std::endl; +} +#endif + +bool upper_trx::driveControl() +{ + trxcon_phyif_rsp r; + trxcon_phyif_cmd cmd; + while (!cmdq_to_phy.spsc_pop(&cmd)) { + cmdq_to_phy.spsc_prep_pop(); + if (g_exit_flag) + return false; + } + + if (g_exit_flag) + return false; + +#ifdef TXDEBUG + print_cmd(cmd.type); +#endif + + switch (cmd.type) { + case TRXCON_PHYIF_CMDT_RESET: + set_ta(0); + break; + case TRXCON_PHYIF_CMDT_POWERON: + if (!mOn) { + mOn = true; + start_lower_ms(); + } + break; + case TRXCON_PHYIF_CMDT_POWEROFF: + break; + case TRXCON_PHYIF_CMDT_MEASURE: + r.type = trxcon_phyif_cmd_type::TRXCON_PHYIF_CMDT_MEASURE; + r.param.measure.band_arfcn = cmd.param.measure.band_arfcn; + // FIXME: do we want to measure anything, considering the transceiver just syncs by.. syncing? + r.param.measure.dbm = -80; + tuneRx(gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 0) * 1000 * 100); + tuneTx(gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 1) * 1000 * 100); + cmdq_from_phy.spsc_push(&r); + break; + case TRXCON_PHYIF_CMDT_SETFREQ_H0: + tuneRx(gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 0) * 1000 * 100); + tuneTx(gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 1) * 1000 * 100); + break; + case TRXCON_PHYIF_CMDT_SETFREQ_H1: + break; + case TRXCON_PHYIF_CMDT_SETSLOT: + break; + case TRXCON_PHYIF_CMDT_SETTA: + set_ta(cmd.param.setta.ta); + break; + } + return false; +} + +void sighandler(int sigset) +{ + // we might get a sigpipe in case the l1ctl ud socket disconnects because mobile quits + if (sigset == SIGPIPE || sigset == SIGINT) { + g_exit_flag = true; + + // we know the flag is atomic and it prevents the trxcon cb handlers from writing + // to the queues, so submit some trash to unblock the threads & exit + trxcon_phyif_cmd cmd = {}; + internal_q_tx_buf b = {}; + txq.spsc_push(&b); + cmdq_to_phy.spsc_push(&cmd); + msleep(200); + + return; + } +} + +int main(int argc, char *argv[]) +{ + auto tall_trxcon_ctx = talloc_init("trxcon context"); + signal(SIGPIPE, sighandler); + signal(SIGINT, sighandler); + + msgb_talloc_ctx_init(tall_trxcon_ctx, 0); + trxc_log_init(tall_trxcon_ctx); + + /* Configure pretty logging */ + log_set_print_extended_timestamp(osmo_stderr_target, 1); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_set_print_level(osmo_stderr_target, 1); + + log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME); + log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END); + + osmo_fsm_log_timeouts(true); + + g_trxcon = trxcon_inst_alloc(tall_trxcon_ctx, 0); + g_trxcon->gsmtap = nullptr; + g_trxcon->phyif = nullptr; + g_trxcon->phy_quirks.fbsb_extend_fns = 866; // 4 seconds, known to work. + + convolve_init(); + convert_init(); + sigProcLibSetup(); + initvita(); + + int status = 0; + auto trx = new upper_trx(); + trx->do_auto_gain = true; + + status = trx->init_dev_and_streams(); + if (status < 0) { + std::cerr << "Error initializing hardware, quitting.." << std::endl; + return -1; + } + set_name_aff_sched(sched_params::thread_names::MAIN); + + if (!trxc_l1ctl_init(tall_trxcon_ctx)) { + std::cerr << "Error initializing l1ctl, quitting.." << std::endl; + return -1; + } + + // blocking, will return when global exit is requested + trx->start_threads(); + trx->main_loop(); + trx->stop_threads(); + trx->stop_upper_threads(); + + return status; +} diff --git a/Transceiver52M/ms/ms_upper.h b/Transceiver52M/ms/ms_upper.h new file mode 100644 index 0000000..06f7c54 --- /dev/null +++ b/Transceiver52M/ms/ms_upper.h @@ -0,0 +1,48 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include <netdb.h> +#include <sys/socket.h> +#include <arpa/inet.h> + +#include "GSMCommon.h" +#include "ms.h" + +class upper_trx : public ms_trx { + volatile bool mOn; + char demodded_softbits[444]; + + // void driveControl(); + bool pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset); + + pthread_t thr_control, thr_tx; + + public: + void start_threads(); + void main_loop(); + void stop_upper_threads(); + + bool driveControl(); + void driveReceiveFIFO(); + void driveTx(); + + upper_trx() : mOn(false){}; +}; diff --git a/Transceiver52M/ms/sch.c b/Transceiver52M/ms/sch.c new file mode 100644 index 0000000..34809a2 --- /dev/null +++ b/Transceiver52M/ms/sch.c @@ -0,0 +1,329 @@ +/* + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> + * (C) 2016 by Tom Tsou <tom.tsou@ettus.com> + * (C) 2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2022 by 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * 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 2 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. + */ + +#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 <osmocom/coding/gsm0503_coding.h> +#include <osmocom/coding/gsm0503_parity.h> + +#include "sch.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" + +/* 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, +}; + +#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; +} + +int gsm_fcch_check_ts(int ts, int fn) { + return ts == 0 && gsm_fcch_check_fn(fn); +} + +int gsm_sch_check_ts(int ts, int fn) { + return ts == 0 && gsm_sch_check_fn(fn); +} + +/* 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; + } + +} + +#pragma GCC diagnostic pop diff --git a/Transceiver52M/ms/sch.h b/Transceiver52M/ms/sch.h new file mode 100644 index 0000000..ea8ba85 --- /dev/null +++ b/Transceiver52M/ms/sch.h @@ -0,0 +1,48 @@ +#pragma once +/* + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> + * (C) 2016 by Tom Tsou <tom.tsou@ettus.com> + * (C) 2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * 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 2 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. + */ + +#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); +int gsm_fcch_check_ts(int ts, int fn); +int gsm_sch_check_ts(int ts, int fn); + +double gsm_fcch_offset(float *burst, int len); + +int float_to_sbit(const float *in, sbit_t *out, float scale, int len); + diff --git a/Transceiver52M/ms/threadpool.h b/Transceiver52M/ms/threadpool.h new file mode 100644 index 0000000..4b1eefd --- /dev/null +++ b/Transceiver52M/ms/threadpool.h @@ -0,0 +1,95 @@ +#pragma once +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <atomic> +#include <vector> +#include <future> +#include <mutex> +#include <queue> +#include "threadsched.h" + +struct single_thread_pool { + std::mutex m; + std::condition_variable cv; + std::atomic<bool> stop_flag; + std::atomic<bool> is_ready; + std::deque<std::function<void()>> wq; + pthread_t worker_thread; + + template <class F> + void add_task(F &&f) + { + std::unique_lock<std::mutex> l(m); + wq.emplace_back(std::forward<F>(f)); + cv.notify_one(); + return; + } + + single_thread_pool() : stop_flag(false), is_ready(false) + { + worker_thread = spawn_worker_thread( + sched_params::thread_names::SCH_SEARCH, + [](void *args) -> void * { + using thist = decltype(this); + thist t = reinterpret_cast<thist>(args); + t->thread_loop(); + return 0; + }, + this); + } + ~single_thread_pool() + { + stop(); + } + + private: + void stop() + { + { + std::unique_lock<std::mutex> l(m); + wq.clear(); + stop_flag = true; + cv.notify_one(); + } + pthread_join(worker_thread, nullptr); + } + + void thread_loop() + { + while (true) { + is_ready = true; + std::function<void()> f; + { + std::unique_lock<std::mutex> l(m); + if (wq.empty()) { + cv.wait(l, [&] { return !wq.empty() || stop_flag; }); + } + if (stop_flag) + return; + is_ready = false; + f = std::move(wq.front()); + wq.pop_front(); + } + f(); + } + } +};
\ No newline at end of file diff --git a/Transceiver52M/ms/threadsched.cpp b/Transceiver52M/ms/threadsched.cpp new file mode 100644 index 0000000..ba5cae7 --- /dev/null +++ b/Transceiver52M/ms/threadsched.cpp @@ -0,0 +1,104 @@ +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <iostream> +#include <thread> + +extern "C" { +#include <pthread.h> +} + +#include "threadsched.h" + +sched_params::target scheduling_target; + +void set_sched_target(sched_params::target t) +{ + scheduling_target = t; +} + +void set_name_aff_sched(std::thread::native_handle_type h, const char *name, int cpunum, int schedtype, int prio) +{ + pthread_setname_np(h, name); + + cpu_set_t cpuset; + + CPU_ZERO(&cpuset); + CPU_SET(cpunum, &cpuset); + + if (pthread_setaffinity_np(h, sizeof(cpuset), &cpuset) < 0) { + std::cerr << name << " affinity: errreur! " << std::strerror(errno); + return exit(0); + } + + sched_param sch_params; + sch_params.sched_priority = prio; + if (pthread_setschedparam(h, schedtype, &sch_params) < 0) { + std::cerr << name << " sched: errreur! " << std::strerror(errno); + return exit(0); + } +} + +static pthread_t do_spawn_thr(const char *name, int cpunum, int schedtype, int prio, worker_func_sig fun, void *arg) +{ + pthread_t thread; + + pthread_attr_t attr; + pthread_attr_init(&attr); + + sched_param sch_params; + sch_params.sched_priority = prio; + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(cpunum, &cpuset); + auto a = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset); + a |= pthread_attr_setschedpolicy(&attr, schedtype); + a |= pthread_attr_setschedparam(&attr, &sch_params); + a |= pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + if (a) + std::cerr << "thread arg rc:" << a << std::endl; + pthread_create(&thread, &attr, fun, arg); + pthread_setname_np(thread, name); + pthread_attr_destroy(&attr); + return thread; +} + +void set_name_aff_sched(std::thread::native_handle_type h, sched_params::thread_names name) +{ + auto tgt = schdp[scheduling_target][name]; + // std::cerr << "scheduling for: " << tgt.name << ":" << tgt.core << std::endl; + set_name_aff_sched(h, tgt.name, tgt.core, tgt.schedtype, tgt.prio); +} + +void set_name_aff_sched(sched_params::thread_names name) +{ + set_name_aff_sched(pthread_self(), name); +} + +pthread_t spawn_worker_thread(sched_params::thread_names name, worker_func_sig fun, void *arg) +{ + auto tgt = schdp[scheduling_target][name]; + // std::cerr << "scheduling for: " << tgt.name << ":" << tgt.core << " prio:" << tgt.prio << std::endl; + return do_spawn_thr(tgt.name, tgt.core, tgt.schedtype, tgt.prio, fun, arg); +} diff --git a/Transceiver52M/ms/threadsched.h b/Transceiver52M/ms/threadsched.h new file mode 100644 index 0000000..7cc9176 --- /dev/null +++ b/Transceiver52M/ms/threadsched.h @@ -0,0 +1,68 @@ +#pragma once +/* + * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +extern "C" { +#include <pthread.h> +#include <sched.h> +} + +static struct sched_params { + enum thread_names { U_CTL = 0, U_RX, U_TX, SCH_SEARCH, MAIN, LEAKCHECK, RXRUN, TXRUN, _THRD_NAME_COUNT }; + enum target { ODROID = 0, PI4 }; + const char *name; + int core; + int schedtype; + int prio; +} schdp[][sched_params::_THRD_NAME_COUNT]{ + { + { "upper_ctrl", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) }, + { "upper_rx", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) - 5 }, + { "upper_tx", 2, SCHED_RR, sched_get_priority_max(SCHED_RR) - 1 }, + + { "sch_search", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, + { "main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, + { "leakcheck", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10 }, + + { "rxrun", 4, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2 }, + { "txrun", 5, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 }, + }, + { + { "upper_ctrl", 1, SCHED_RR, sched_get_priority_max(SCHED_RR) }, + { "upper_rx", 1, SCHED_RR, sched_get_priority_max(SCHED_RR) - 5 }, + { "upper_tx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 }, + + { "sch_search", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, + { "main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5 }, + { "leakcheck", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10 }, + + { "rxrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2 }, + { "txrun", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1 }, + }, +}; + +void set_sched_target(sched_params::target t); + +using worker_func_sig = void *(*)(void *); + +void set_name_aff_sched(sched_params::thread_names name); + +pthread_t spawn_worker_thread(sched_params::thread_names name, worker_func_sig fun, void *arg); diff --git a/Transceiver52M/ms/uhd_specific.h b/Transceiver52M/ms/uhd_specific.h new file mode 100644 index 0000000..151c002 --- /dev/null +++ b/Transceiver52M/ms/uhd_specific.h @@ -0,0 +1,279 @@ +#pragma once +/* + * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Eric Wild <ewild@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <uhd/version.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/types/metadata.hpp> +#include <complex> +#include <cstring> +#include <iostream> +#include <thread> + +#include <Timeval.h> +#include <vector> + +using blade_sample_type = std::complex<int16_t>; +const int SAMPLE_SCALE_FACTOR = 1; + +struct uhd_buf_wrap { + double rxticks; + size_t num_samps; + uhd::rx_metadata_t *md; + blade_sample_type *buf; + auto actual_samples_per_buffer() + { + return num_samps; + } + long get_first_ts() + { + return md->time_spec.to_ticks(rxticks); + } + int readall(blade_sample_type *outaddr) + { + memcpy(outaddr, buf, num_samps * sizeof(blade_sample_type)); + return num_samps; + } + int read_n(blade_sample_type *outaddr, int start, int num) + { + assert(start >= 0); + auto to_read = std::min((int)num_samps - start, num); + assert(to_read >= 0); + memcpy(outaddr, buf + start, to_read * sizeof(blade_sample_type)); + return to_read; + } +}; + +using dev_buf_t = uhd_buf_wrap; +using bh_fn_t = std::function<int(dev_buf_t *)>; + +template <typename T> +struct uhd_hw { + uhd::usrp::multi_usrp::sptr dev; + uhd::rx_streamer::sptr rx_stream; + uhd::tx_streamer::sptr tx_stream; + blade_sample_type *one_pkt_buf; + std::vector<blade_sample_type *> pkt_ptrs; + size_t rx_spp; + double rxticks; + const unsigned int rxFullScale, txFullScale; + const int rxtxdelay; + float rxgain, txgain; + static std::atomic<bool> stop_lower_threads_flag; + double rxfreq_cache, txfreq_cache; + + virtual ~uhd_hw() + { + delete[] one_pkt_buf; + } + uhd_hw() : rxFullScale(32767), txFullScale(32767 * 0.3), rxtxdelay(-67), rxfreq_cache(0), txfreq_cache(0) + { + } + + void close_device() + { + } + + bool tuneTx(double freq, size_t chan = 0) + { + if (txfreq_cache == freq) + return true; + msleep(25); + dev->set_tx_freq(freq, chan); + txfreq_cache = freq; + msleep(25); + return true; + }; + bool tuneRx(double freq, size_t chan = 0) + { + if (rxfreq_cache == freq) + return true; + msleep(25); + dev->set_rx_freq(freq, chan); + rxfreq_cache = freq; + msleep(25); + return true; + }; + bool tuneRxOffset(double offset, size_t chan = 0) + { + return true; + }; + + double setRxGain(double dB, size_t chan = 0) + { + rxgain = dB; + msleep(25); + dev->set_rx_gain(dB, chan); + msleep(25); + return dB; + }; + double setTxGain(double dB, size_t chan = 0) + { + txgain = dB; + msleep(25); + dev->set_tx_gain(dB, chan); + msleep(25); + return dB; + }; + int setPowerAttenuation(int atten, size_t chan = 0) + { + return atten; + }; + + int init_device(bh_fn_t rxh, bh_fn_t txh) + { + auto const lock_delay_ms = 500; + auto clock_lock_attempts = 15; // x lock_delay_ms + auto const mcr = 26e6; + auto const rate = (1625e3 / 6) * 4; + auto const ref = "external"; + auto const gain = 35; + auto const freq = 931.4e6; // 936.8e6 + auto bw = 0.5e6; + auto const channel = 0; + // aligned to blade: 1020 samples per transfer + std::string args = { "recv_frame_size=4092,send_frame_size=4092" }; + + dev = uhd::usrp::multi_usrp::make(args); + std::cout << "Using Device: " << dev->get_pp_string() << std::endl; + dev->set_clock_source(ref); + dev->set_master_clock_rate(mcr); + dev->set_rx_rate(rate, channel); + dev->set_tx_rate(rate, channel); + uhd::tune_request_t tune_request(freq, 0); + dev->set_rx_freq(tune_request, channel); + dev->set_rx_gain(gain, channel); + dev->set_tx_gain(60, channel); + dev->set_rx_bandwidth(bw, channel); + dev->set_tx_bandwidth(bw, channel); + + while (!(dev->get_rx_sensor("lo_locked", channel).to_bool() && + dev->get_mboard_sensor("ref_locked").to_bool()) && + clock_lock_attempts > 0) { + std::cerr << "clock source lock attempts remaining: " << clock_lock_attempts << ".." + << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(lock_delay_ms)); + clock_lock_attempts--; + } + + if (clock_lock_attempts <= 0) { + std::cerr << "Error locking clock, gpsdo missing? quitting.." << std::endl; + return -1; + } + + uhd::stream_args_t stream_args("sc16", "sc16"); + rx_stream = dev->get_rx_stream(stream_args); + uhd::stream_args_t stream_args2("sc16", "sc16"); + tx_stream = dev->get_tx_stream(stream_args2); + + rx_spp = rx_stream->get_max_num_samps(); + rxticks = dev->get_rx_rate(); + assert(rxticks == dev->get_tx_rate()); + one_pkt_buf = new blade_sample_type[rx_spp]; + pkt_ptrs = { 1, &one_pkt_buf[0] }; + return 0; + } + + void actually_enable_streams() + { + // nop: stream cmd in handler + } + + void *rx_cb(bh_fn_t burst_handler) + { + void *ret = nullptr; + static int to_skip = 0; + + uhd::rx_metadata_t md; + auto num_rx_samps = rx_stream->recv(pkt_ptrs.front(), rx_spp, md, 1.0, true); + + if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) { + std::cerr << boost::format("Timeout while streaming") << std::endl; + exit(0); + } + if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW) { + std::cerr << boost::format("Got an overflow indication\n") << std::endl; + exit(0); + } + if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE) { + std::cerr << str(boost::format("Receiver error: %s") % md.strerror()); + exit(0); + } + + dev_buf_t rcd = { rxticks, num_rx_samps, &md, &one_pkt_buf[0] }; + + if (to_skip < 120) // prevents weird overflows on startup + to_skip++; + else { + burst_handler(&rcd); + } + + return ret; + } + + auto get_rx_burst_handler_fn(bh_fn_t burst_handler) + { + // C cb -> ghetto closure capture, which is fine, the args never change. + static auto rx_burst_cap_this = this; + static auto rx_burst_cap_bh = burst_handler; + auto fn = [](void *args) -> void * { + pthread_setname_np(pthread_self(), "rxrun"); + + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + stream_cmd.stream_now = true; + stream_cmd.time_spec = uhd::time_spec_t(); + rx_burst_cap_this->rx_stream->issue_stream_cmd(stream_cmd); + + while (!rx_burst_cap_this->stop_lower_threads_flag) { + rx_burst_cap_this->rx_cb(rx_burst_cap_bh); + } + return 0; + }; + return fn; + } + auto get_tx_burst_handler_fn(bh_fn_t burst_handler) + { + auto fn = [](void *args) -> void * { + // dummy + return 0; + }; + return fn; + } + void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts) + { + uhd::tx_metadata_t m = {}; + m.end_of_burst = true; + m.start_of_burst = true; + m.has_time_spec = true; + m.time_spec = m.time_spec.from_ticks(ts + rxtxdelay, rxticks); // uhd specific b210 delay! + std::vector<void *> ptrs(1, buffer); + + tx_stream->send(ptrs, len, m, 1.0); +#ifdef DBGXX + uhd::async_metadata_t async_md; + bool tx_ack = false; + while (!tx_ack && tx_stream->recv_async_msg(async_md)) { + tx_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK); + } + std::cout << (tx_ack ? "yay" : "nay") << " " << async_md.time_spec.to_ticks(rxticks) << std::endl; +#endif + } +};
\ No newline at end of file diff --git a/Transceiver52M/osmo-trx.cpp b/Transceiver52M/osmo-trx.cpp index 4a92447..8f5cc85 100644 --- a/Transceiver52M/osmo-trx.cpp +++ b/Transceiver52M/osmo-trx.cpp @@ -12,10 +12,6 @@ * 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 @@ -81,6 +77,24 @@ static struct ctrl_handle *g_ctrlh; static RadioDevice *usrp; static RadioInterface *radio; +/* adjusts read timestamp offset to make the viterbi equalizer happy by including the start tail bits */ +template <typename B> +class rif_va_wrapper : public B { + bool use_va; + + public: + template <typename... Args> + rif_va_wrapper(bool use_va, Args &&...args) : B(std::forward<Args>(args)...), use_va(use_va) + { + } + bool start() override + { + auto rv = B::start(); + B::readTimestamp -= use_va ? 20 : 0; + return rv; + }; +}; + /* Create radio interface * The interface consists of sample rate changes, frequency shifts, * channel multiplexing, and other conversions. The transceiver core @@ -95,17 +109,17 @@ RadioInterface *makeRadioInterface(struct trx_ctx *trx, switch (type) { case RadioDevice::NORMAL: - radio = new RadioInterface(usrp, trx->cfg.tx_sps, - trx->cfg.rx_sps, trx->cfg.num_chans); + radio = new rif_va_wrapper<RadioInterface>(trx->cfg.use_va, 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); + radio = new rif_va_wrapper<RadioInterfaceResamp>(trx->cfg.use_va, 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 rif_va_wrapper<RadioInterfaceMulti>(trx->cfg.use_va, usrp, trx->cfg.tx_sps, trx->cfg.rx_sps, + trx->cfg.num_chans); break; default: LOG(ALERT) << "Unsupported radio interface configuration"; @@ -144,12 +158,8 @@ int makeTransceiver(struct trx_ctx *trx, RadioInterface *radio) { VectorFIFO *fifo; - transceiver = new Transceiver(trx->cfg.base_port, trx->cfg.bind_addr, - trx->cfg.remote_addr, trx->cfg.tx_sps, - trx->cfg.rx_sps, trx->cfg.num_chans, GSM::Time(3,0), - radio, trx->cfg.rssi_offset, trx->cfg.stack_size); - if (!transceiver->init(trx->cfg.filler, trx->cfg.rtsc, - trx->cfg.rach_delay, trx->cfg.egprs, trx->cfg.ext_rach)) { + transceiver = new Transceiver(&trx->cfg, GSM::Time(3,0), radio); + if (!transceiver->init()) { LOG(ALERT) << "Failed to initialize transceiver"; return -1; } @@ -181,6 +191,17 @@ static void sig_handler(int signo) 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); @@ -246,10 +267,13 @@ static void setup_signal_handlers() static void print_help() { - fprintf(stdout, "Options:\n" - " -h, --help This text\n" - " -C, --config Filename The config file to use\n" - " -V, --version Print the version of OsmoTRX\n" + 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" ); } @@ -260,16 +284,44 @@ static void print_deprecated(char opt) << " 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} }; @@ -280,6 +332,9 @@ static void handle_options(int argc, char **argv, struct trx_ctx* trx) 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); @@ -346,6 +401,7 @@ static void handle_options(int argc, char **argv, struct trx_ctx* trx) case 'R': print_deprecated(option); trx->cfg.rssi_offset = atof(optarg); + trx->cfg.force_rssi_offset = true; break; case 'S': print_deprecated(option); @@ -427,6 +483,12 @@ int trx_validate_config(struct trx_ctx *trx) return -1; } + if (trx->cfg.use_va && + (trx->cfg.egprs || trx->cfg.multi_arfcn || trx->cfg.tx_sps != 4 || trx->cfg.rx_sps != 4)) { + LOG(ERROR) << "Viterbi equalizer only works for gmsk with 4 tx/rx samples per symbol!"; + return -1; + } + return 0; } @@ -447,6 +509,38 @@ static int set_sched_rr(unsigned int prio) 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; @@ -468,8 +562,10 @@ static void print_config(struct trx_ctx *trx) 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 << " Tuning offset........... " << trx->cfg.offset << std::endl; - ost << " RSSI to dBm offset...... " << trx->cfg.rssi_offset << 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++) { @@ -499,24 +595,14 @@ static void trx_stop() 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); + usrp = RadioDevice::make(iface, &trx->cfg); + type = usrp->open(); if (type < 0) { LOG(ALERT) << "Failed to create radio device" << std::endl; goto shutdown; @@ -554,35 +640,6 @@ int main(int argc, char *argv[]) g_trx_ctx = vty_trx_ctx_alloc(tall_trx_ctx); -#ifdef HAVE_SSE3 - printf("Info: SSE3 support compiled in"); -#ifdef HAVE___BUILTIN_CPU_SUPPORTS - if (__builtin_cpu_supports("sse3")) - printf(" and supported by CPU\n"); - else - printf(", but not supported by CPU\n"); -#else - printf(", but runtime SIMD detection disabled\n"); -#endif -#endif - -#ifdef HAVE_SSE4_1 - printf("Info: SSE4.1 support compiled in"); -#ifdef HAVE___BUILTIN_CPU_SUPPORTS - if (__builtin_cpu_supports("sse4.1")) - printf(" and supported by CPU\n"); - else - printf(", but not supported by CPU\n"); -#else - printf(", 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!") - printf("Built without atomic operation support. Using Mutex, it may affect performance!\n"); -#endif - convolve_init(); convert_init(); @@ -608,11 +665,11 @@ int main(int argc, char *argv[]) exit(2); } - rc = telnet_init_dynif(tall_trx_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_TRX); + rc = telnet_init_default(tall_trx_ctx, NULL, 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); + g_ctrlh = ctrl_interface_setup(NULL, OSMO_CTRL_PORT_TRX, NULL); if (!g_ctrlh) { LOG(ERROR) << "Failed to create CTRL interface.\n"; exit(1); @@ -629,6 +686,7 @@ int main(int argc, char *argv[]) " 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) { diff --git a/Transceiver52M/proto_trxd.h b/Transceiver52M/proto_trxd.h index d72cfdd..c250a74 100644 --- a/Transceiver52M/proto_trxd.h +++ b/Transceiver52M/proto_trxd.h @@ -48,9 +48,8 @@ struct trxd_hdr_common { reserved:1, version:4; #elif OSMO_IS_BIG_ENDIAN - uint8_t version:4, - reserved:1, - tn:3; +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ + uint8_t version:4, reserved:1, tn:3; #endif uint32_t fn; /* big endian */ } __attribute__ ((packed)); @@ -86,9 +85,8 @@ struct trxd_hdr_v1_specific { modulation:4, idle:1; #elif OSMO_IS_BIG_ENDIAN - uint8_t idle:1, - modulation:4, - tsc:3; +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ + uint8_t idle:1, modulation:4, tsc:3; #endif int16_t ci; /* big endian, in centiBels */ } __attribute__ ((packed)); diff --git a/Transceiver52M/radioBuffer.cpp b/Transceiver52M/radioBuffer.cpp index 62f6553..ec868e5 100644 --- a/Transceiver52M/radioBuffer.cpp +++ b/Transceiver52M/radioBuffer.cpp @@ -28,7 +28,7 @@ RadioBuffer::RadioBuffer(size_t numSegments, size_t segmentLen, size_t hLen, bool outDirection) - : writeIndex(0), readIndex(0), availSamples(0) + : writeIndex(0), readIndex(0), availSamples(0), segments(numSegments) { if (!outDirection) hLen = 0; @@ -36,7 +36,6 @@ RadioBuffer::RadioBuffer(size_t numSegments, size_t segmentLen, buffer = new float[2 * (hLen + numSegments * segmentLen)]; bufferLen = numSegments * segmentLen; - segments.resize(numSegments); for (size_t i = 0; i < numSegments; i++) segments[i] = &buffer[2 * (hLen + i * segmentLen)]; diff --git a/Transceiver52M/radioInterface.cpp b/Transceiver52M/radioInterface.cpp index 64a6f21..311af34 100644 --- a/Transceiver52M/radioInterface.cpp +++ b/Transceiver52M/radioInterface.cpp @@ -39,9 +39,10 @@ extern "C" { RadioInterface::RadioInterface(RadioDevice *wDevice, size_t tx_sps, size_t rx_sps, size_t chans, 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) + : mSPSTx(tx_sps), mSPSRx(rx_sps), mChans(chans), mReceiveFIFO(mChans), mDevice(wDevice), + sendBuffer(mChans), recvBuffer(mChans), convertRecvBuffer(mChans), + convertSendBuffer(mChans), powerScaling(mChans), underrun(false), overrun(false), + writeTimestamp(0), readTimestamp(0), receiveOffset(wReceiveOffset), mOn(false) { mClock.set(wStartTime); } @@ -58,15 +59,6 @@ bool RadioInterface::init(int type) return false; } - close(); - - sendBuffer.resize(mChans); - recvBuffer.resize(mChans); - convertSendBuffer.resize(mChans); - convertRecvBuffer.resize(mChans); - mReceiveFIFO.resize(mChans); - powerScaling.resize(mChans); - for (size_t i = 0; i < mChans; i++) { sendBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSTx, 0, true); recvBuffer[i] = new RadioBuffer(NUMCHUNKS, CHUNK * mSPSRx, 0, false); @@ -322,6 +314,11 @@ double RadioInterface::setRxGain(double dB, size_t chan) return mDevice->setRxGain(dB, chan); } +double RadioInterface::rssiOffset(size_t chan) +{ + return mDevice->rssiOffset(chan); +} + /* Receive a timestamped chunk from the device */ int RadioInterface::pullBuffer() { diff --git a/Transceiver52M/radioInterface.h b/Transceiver52M/radioInterface.h index 8e5f4c1..b05af78 100644 --- a/Transceiver52M/radioInterface.h +++ b/Transceiver52M/radioInterface.h @@ -31,6 +31,9 @@ static const unsigned gSlotLen = 148; ///< number of symbols per slot, not class RadioInterface { protected: + size_t mSPSTx; + size_t mSPSRx; + size_t mChans; Thread mAlignRadioServiceLoopThread; ///< thread that synchronizes transmit and receive sections @@ -38,10 +41,6 @@ protected: RadioDevice *mDevice; ///< the USRP object - size_t mSPSTx; - size_t mSPSRx; - size_t mChans; - std::vector<RadioBuffer *> sendBuffer; std::vector<RadioBuffer *> recvBuffer; @@ -76,7 +75,7 @@ private: public: /** start the interface */ - bool start(); + virtual bool start(); bool stop(); /** initialization */ @@ -109,6 +108,9 @@ public: /** set receive gain */ virtual double setRxGain(double dB, size_t chan = 0); + /** return base RSSI offset to apply for received samples **/ + virtual double rssiOffset(size_t chan = 0); + /** drive transmission of GSM bursts */ void driveTransmitRadio(std::vector<signalVector *> &bursts, std::vector<bool> &zeros); @@ -149,7 +151,7 @@ private: public: RadioInterfaceResamp(RadioDevice* wDevice, size_t tx_sps, size_t rx_sps); - ~RadioInterfaceResamp(); + virtual ~RadioInterfaceResamp(); bool init(int type); void close(); @@ -182,7 +184,7 @@ private: public: RadioInterfaceMulti(RadioDevice* radio, size_t tx_sps, size_t rx_sps, size_t chans = 1); - ~RadioInterfaceMulti(); + virtual ~RadioInterfaceMulti(); bool init(int type); void close(); @@ -190,4 +192,5 @@ public: bool tuneTx(double freq, size_t chan); bool tuneRx(double freq, size_t chan); virtual double setRxGain(double dB, size_t chan); + virtual double rssiOffset(size_t chan = 0); }; diff --git a/Transceiver52M/radioInterfaceMulti.cpp b/Transceiver52M/radioInterfaceMulti.cpp index 1ca3fbb..a7195b4 100644 --- a/Transceiver52M/radioInterfaceMulti.cpp +++ b/Transceiver52M/radioInterfaceMulti.cpp @@ -44,8 +44,9 @@ extern "C" { RadioInterfaceMulti::RadioInterfaceMulti(RadioDevice *radio, size_t tx_sps, size_t rx_sps, size_t chans) : RadioInterface(radio, tx_sps, rx_sps, chans), - outerSendBuffer(NULL), outerRecvBuffer(NULL), - dnsampler(NULL), upsampler(NULL), channelizer(NULL), synthesis(NULL) + outerSendBuffer(NULL), outerRecvBuffer(NULL), history(mChans), active(MCHANS, false), + rx_freq_state(mChans), tx_freq_state(mChans), dnsampler(NULL), upsampler(NULL), channelizer(NULL), + synthesis(NULL) { } @@ -70,12 +71,16 @@ void RadioInterfaceMulti::close() channelizer = NULL; synthesis = NULL; - mReceiveFIFO.resize(0); - powerScaling.resize(0); - history.resize(0); - active.resize(0); - rx_freq_state.resize(0); - tx_freq_state.resize(0); + + for (std::vector<signalVector*>::iterator it = history.begin(); it != history.end(); ++it) + delete *it; + + mReceiveFIFO.clear(); + powerScaling.clear(); + history.clear(); + active.clear(); + rx_freq_state.clear(); + tx_freq_state.clear(); RadioInterface::close(); } @@ -148,20 +153,9 @@ bool RadioInterfaceMulti::init(int type) return false; } - close(); - - sendBuffer.resize(mChans); - recvBuffer.resize(mChans); convertSendBuffer.resize(1); convertRecvBuffer.resize(1); - mReceiveFIFO.resize(mChans); - powerScaling.resize(mChans); - history.resize(mChans); - rx_freq_state.resize(mChans); - tx_freq_state.resize(mChans); - active.resize(MCHANS, false); - /* 4 == sps */ inchunk = RESAMP_INRATE * 4; outchunk = RESAMP_OUTRATE * 4; @@ -446,6 +440,11 @@ double RadioInterfaceMulti::setRxGain(double db, size_t chan) return mDevice->getRxGain(); } +double RadioInterfaceMulti::rssiOffset(size_t chan) +{ + return mDevice->rssiOffset(0); +} + int RadioInterfaceMulti::setPowerAttenuation(int atten, size_t chan) { return RadioInterface::setPowerAttenuation(atten, 0); diff --git a/Transceiver52M/radioInterfaceResamp.cpp b/Transceiver52M/radioInterfaceResamp.cpp index b92432f..869acd0 100644 --- a/Transceiver52M/radioInterfaceResamp.cpp +++ b/Transceiver52M/radioInterfaceResamp.cpp @@ -98,15 +98,6 @@ bool RadioInterfaceResamp::init(int type) { float cutoff = 1.0f; - close(); - - sendBuffer.resize(1); - recvBuffer.resize(1); - convertSendBuffer.resize(1); - convertRecvBuffer.resize(1); - mReceiveFIFO.resize(1); - powerScaling.resize(1); - switch (type) { case RadioDevice::RESAMP_64M: resamp_inrate = RESAMP_64M_INRATE; diff --git a/Transceiver52M/radioVector.cpp b/Transceiver52M/radioVector.cpp index 68e42c5..acefc97 100644 --- a/Transceiver52M/radioVector.cpp +++ b/Transceiver52M/radioVector.cpp @@ -76,22 +76,25 @@ bool radioVector::setVector(signalVector *vector, size_t chan) return true; } -noiseVector::noiseVector(size_t size) +avgVector::avgVector(size_t size) : std::vector<float>(size), itr(0) { } -float noiseVector::avg() const +float avgVector::avg() const { float val = 0.0; + if (!size()) + return 0.0f; + for (size_t i = 0; i < size(); i++) val += (*this)[i]; return val / (float) size(); } -bool noiseVector::insert(float val) +bool avgVector::insert(float val) { if (!size()) return false; diff --git a/Transceiver52M/radioVector.h b/Transceiver52M/radioVector.h index 84e3987..90db626 100644 --- a/Transceiver52M/radioVector.h +++ b/Transceiver52M/radioVector.h @@ -48,9 +48,9 @@ private: GSM::Time mTime; }; -class noiseVector : std::vector<float> { +class avgVector : std::vector<float> { public: - noiseVector(size_t size = 0); + avgVector(size_t size = 0); bool insert(float val); float avg() const; diff --git a/Transceiver52M/sigProcLib.cpp b/Transceiver52M/sigProcLib.cpp index 04f7e30..5fac365 100644 --- a/Transceiver52M/sigProcLib.cpp +++ b/Transceiver52M/sigProcLib.cpp @@ -34,6 +34,7 @@ #include "Resampler.h" extern "C" { +#include <osmocom/core/panic.h> #include "convolve.h" #include "scale.h" #include "mult.h" @@ -128,6 +129,8 @@ 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 CorrelationSequence *gDummySequence = NULL; static PulseSequence *GSMPulse1 = NULL; static PulseSequence *GSMPulse4 = NULL; @@ -150,6 +153,12 @@ void sigProcLibDestroy() gRACHSequences[i] = NULL; } + delete gSCHSequence; + gSCHSequence = NULL; + + delete gDummySequence; + gDummySequence = NULL; + delete GMSKRotation1; delete GMSKReverseRotation1; delete GMSKRotation4; @@ -314,6 +323,7 @@ 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; @@ -1288,6 +1298,77 @@ release: return status; } +static bool generateDummyMidamble(int sps) +{ + bool status = true; + float toa; + complex *data = NULL; + signalVector *autocorr = NULL, *midamble = NULL; + signalVector *midMidamble = NULL, *_midMidamble = NULL; + + delete gDummySequence; + + /* Use middle 16 bits of each TSC. Correlation sequence is not pulse shaped */ + midMidamble = modulateBurst(gDummyBurstTSC.segment(5,16), 0, sps, true); + if (!midMidamble) + return false; + + /* Simulated receive sequence is pulse shaped */ + midamble = modulateBurst(gDummyBurstTSC, 0, sps, false); + if (!midamble) { + status = false; + goto release; + } + + // NOTE: Because ideal TSC 16-bit midamble is 66 symbols into burst, + // the ideal TSC has an + 180 degree phase shift, + // due to the pi/2 frequency shift, that + // needs to be accounted for. + // 26-midamble is 61 symbols into burst, has +90 degree phase shift. + scaleVector(*midMidamble, complex(-1.0, 0.0)); + scaleVector(*midamble, complex(0.0, 1.0)); + + conjugateVector(*midMidamble); + + /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */ + data = (complex *) convolve_h_alloc(midMidamble->size()); + _midMidamble = new signalVector(data, 0, midMidamble->size(), convolve_h_alloc, free); + _midMidamble->setAligned(true); + midMidamble->copyTo(*_midMidamble); + + autocorr = convolve(midamble, _midMidamble, NULL, NO_DELAY); + if (!autocorr) { + status = false; + goto release; + } + + gDummySequence = new CorrelationSequence; + gDummySequence->sequence = _midMidamble; + gDummySequence->gain = peakDetect(*autocorr, &toa, NULL); + + /* For 1 sps only + * (Half of correlation length - 1) + midpoint of pulse shape + remainder + * 13.5 = (16 / 2 - 1) + 1.5 + (26 - 10) / 2 + */ + if (sps == 1) + gDummySequence->toa = toa - 13.5; + else + gDummySequence->toa = 0; + +release: + delete autocorr; + delete midamble; + delete midMidamble; + + if (!status) { + delete _midMidamble; + free(data); + gDummySequence = NULL; + } + + return status; +} + static CorrelationSequence *generateEdgeMidamble(int tsc) { complex *data = NULL; @@ -1383,6 +1464,69 @@ 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(), convolve_h_alloc, free); + _seq1->setAligned(true); + seq1->copyTo(*_seq1); + + 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); + + /* 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 */ @@ -1440,14 +1584,15 @@ float energyDetect(const signalVector &rxBurst, unsigned windowLength) return energy/windowLength; } -static signalVector *downsampleBurst(const signalVector &burst) +static signalVector *downsampleBurst(const signalVector &burst, int in_len = DOWNSAMPLE_IN_LEN, + int out_len = DOWNSAMPLE_OUT_LEN) { - signalVector in(DOWNSAMPLE_IN_LEN, dnsampler->len()); - signalVector *out = new signalVector(DOWNSAMPLE_OUT_LEN); - burst.copyToSegment(in, 0, DOWNSAMPLE_IN_LEN); + signalVector in(in_len, dnsampler->len()); + // gSCHSequence->sequence->size(), ensure next conv has no realloc + signalVector *out = new signalVector(out_len, 64); + burst.copyToSegment(in, 0, in_len); - if (dnsampler->rotate((float *) in.begin(), DOWNSAMPLE_IN_LEN, - (float *) out->begin(), DOWNSAMPLE_OUT_LEN) < 0) { + if (dnsampler->rotate((float *)in.begin(), in_len, (float *)out->begin(), out_len) < 0) { delete out; out = NULL; } @@ -1460,25 +1605,36 @@ static signalVector *downsampleBurst(const signalVector &burst) * It is computed from the training sequence of each received burst, * by comparing the "ideal" training sequence with the actual one. */ -static float computeCI(const signalVector *burst, CorrelationSequence *sync, - float toa, int start, complex xcorr) +static float computeCI(const signalVector *burst, const CorrelationSequence *sync, + float toa, int start, const complex &xcorr) { + const int N = sync->sequence->size(); float S, C; - int ps; - /* Integer position where the sequence starts */ - ps = start + 1 - sync->sequence->size() + (int)roundf(toa); + 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; + + if (ps + N > (int)burst->size()) + return 0; /* Estimate Signal power */ S = 0.0f; - for (int i=0, j=ps; i<(int)sync->sequence->size(); i++,j++) + for (int i=0, j=ps; i<(int)N; i++,j++) S += (*burst)[j].norm2(); - S /= sync->sequence->size(); + S /= N; /* Esimate Carrier power */ - C = xcorr.norm2() / ((sync->sequence->size() - 1) * sync->gain.abs()); - - /* Interference = Signal - Carrier, so C/I = C / (S - C) */ + C = xcorr.norm2() / ((N - 1) * sync->gain.abs()); + + /* Interference = Signal - Carrier, so C/I = C / (S - C). + * Calculated in dB: + * C/I_dB = 10 * log10(C/I) + * C/I_dB = 10 * (1/log2(10)) * log2(C/I) + * C/I_dB = 10 * 0.30103 * log2(C/I) + * C/I_dB = 3.0103 * log2(C/I) + */ return 3.0103f * log2f(C / (S - C)); } @@ -1491,7 +1647,7 @@ static float computeCI(const signalVector *burst, CorrelationSequence *sync, * and we run full interpolating peak detection. */ static int detectBurst(const signalVector &burst, - signalVector &corr, CorrelationSequence *sync, + signalVector &corr, const CorrelationSequence *sync, float thresh, int sps, int start, int len, struct estim_burst_params *ebp) { @@ -1500,12 +1656,18 @@ static int detectBurst(const signalVector &burst, complex xcorr; int rc = 1; - if (sps == 4) { - dec = downsampleBurst(burst); - corr_in = dec; - sps = 1; - } else { + switch (sps) { + case 1: corr_in = &burst; + break; + case 4: + dec = downsampleBurst(burst); + /* Running at the downsampled rate at this point: */ + corr_in = dec; + sps = 1; + break; + default: + osmo_panic("%s:%d SPS %d not supported! Only 1 or 4 supported", __FILE__, __LINE__, sps); } /* Correlate */ @@ -1515,9 +1677,6 @@ static int detectBurst(const signalVector &burst, goto del_ret; } - /* Running at the downsampled rate at this point */ - sps = 1; - /* Peak detection - place restrictions at correlation edges */ ebp->amp = fastPeakDetect(corr, &ebp->toa); @@ -1586,7 +1745,7 @@ static int detectGeneralBurst(const signalVector &rxBurst, float thresh, int sps // and only report clipping if we can't demod. float maxAmpl = maxAmplitude(rxBurst); if (maxAmpl > CLIP_THRESH) { - LOG(DEBUG) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl; + LOG(INFO) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl; clipping = true; } @@ -1643,6 +1802,80 @@ 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; + complex _amp; + 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_BUFFER: + target = 1; + head = 0; + tail = (12 * 8 * 625) / 4; // 12 frames, downsampled /4 to 1 sps + 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; + signalVector corr(len); + + signalVector *dec = downsampleBurst(burst, len * 4, len); + rc = detectBurst(*dec, corr, sync, thresh, 1, start, len, ebp); + delete dec; + + if (rc < 0) { + return -1; + } else if (!rc) { + ebp->amp = 0.0f; + ebp->toa = 0.0f; + return 0; + } + + if (state == sch_detect_type::SCH_DETECT_BUFFER) + ebp->toa = ebp->toa - (3 + 39 + 64); + else { + /* Subtract forward search bits from delay */ + ebp->toa = ebp->toa - head; + } + + return rc; +} + +static int detectDummyBurst(const signalVector &burst, float threshold, + int sps, unsigned max_toa, struct estim_burst_params *ebp) +{ + int rc, target, head, tail; + CorrelationSequence *sync; + + target = 3 + 58 + 16 + 5; + head = 10; + tail = 6 + max_toa; + sync = gDummySequence; + + ebp->tsc = 0; + rc = detectGeneralBurst(burst, threshold, sps, target, head, tail, sync, ebp); + return rc; +} + /* * Normal burst detection * @@ -1661,7 +1894,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]; @@ -1710,6 +1943,9 @@ int detectAnyBurst(const signalVector &burst, unsigned tsc, float threshold, case RACH: rc = detectRACHBurst(burst, threshold, sps, max_toa, type == EXT_RACH, ebp); break; + case IDLE: + rc = detectDummyBurst(burst, threshold, sps, max_toa, ebp); + break; default: LOG(ERR) << "Invalid correlation type"; } @@ -1792,15 +2028,15 @@ static SoftVector *signalToSoftVector(signalVector *dec) * stages. */ static signalVector *demodCommon(const signalVector &burst, int sps, - complex chan, float toa) + const struct estim_burst_params *ebp) { signalVector *delay, *dec; if ((sps != 1) && (sps != 4)) return NULL; - delay = delayVector(&burst, NULL, -toa * (float) sps); - scaleVector(*delay, (complex) 1.0 / chan); + delay = delayVector(&burst, NULL, -ebp->toa * (float) sps); + scaleVector(*delay, (complex) 1.0 / ebp->amp); if (sps == 1) return delay; @@ -1816,13 +2052,13 @@ static signalVector *demodCommon(const signalVector &burst, int sps, * 4 SPS (if activated) to minimize distortion through the fractional * delay filters. Symbol rotation and after always operates at 1 SPS. */ -static SoftVector *demodGmskBurst(const signalVector &rxBurst, - int sps, complex channel, float TOA) +static SoftVector *demodGmskBurst(const signalVector &rxBurst, int sps, + const struct estim_burst_params *ebp) { SoftVector *bits; signalVector *dec; - dec = demodCommon(rxBurst, sps, channel, TOA); + dec = demodCommon(rxBurst, sps, ebp); if (!dec) return NULL; @@ -1835,29 +2071,51 @@ static SoftVector *demodGmskBurst(const signalVector &rxBurst, return bits; } +static float computeEdgeCI(const signalVector *rot) +{ + float err_pwr = 0.0f; + float step = 2.0f * M_PI_F / 8.0f; + + for (size_t i = 8; i < rot->size() - 8; i++) { + /* Compute the ideal symbol */ + complex sym = (*rot)[i]; + float phase = step * roundf(sym.arg() / step); + complex ideal = complex(cos(phase), sin(phase)); + + /* Compute the error vector */ + complex err = ideal - sym; + + /* Accumulate power */ + err_pwr += err.norm2(); + } + + return 3.0103f * log2f(1.0f * (rot->size() - 16) / err_pwr); +} + /* * Demodulate an 8-PSK burst. Prior to symbol rotation, operate at * 4 SPS (if activated) to minimize distortion through the fractional * delay filters. Symbol rotation and after always operates at 1 SPS. * * Allow 1 SPS demodulation here, but note that other parts of the - * transceiver restrict EDGE operatoin to 4 SPS - 8-PSK distortion + * transceiver restrict EDGE operation to 4 SPS - 8-PSK distortion * through the fractional delay filters at 1 SPS renders signal * nearly unrecoverable. */ -static SoftVector *demodEdgeBurst(const signalVector &burst, - int sps, complex chan, float toa) +static SoftVector *demodEdgeBurst(const signalVector &burst, int sps, + struct estim_burst_params *ebp) { SoftVector *bits; signalVector *dec, *rot, *eq; - dec = demodCommon(burst, sps, chan, toa); + dec = demodCommon(burst, sps, ebp); if (!dec) return NULL; /* Equalize and derotate */ eq = convolve(dec, GSMPulse4->c0_inv, NULL, NO_DELAY); rot = derotateEdgeBurst(*eq, 1); + ebp->ci = computeEdgeCI(rot); /* Soft slice and normalize */ bits = softSliceEdgeBurst(*rot); @@ -1869,13 +2127,13 @@ static SoftVector *demodEdgeBurst(const signalVector &burst, return bits; } -SoftVector *demodAnyBurst(const signalVector &burst, int sps, complex amp, - float toa, CorrType type) +SoftVector *demodAnyBurst(const signalVector &burst, CorrType type, + int sps, struct estim_burst_params *ebp) { if (type == EDGE) - return demodEdgeBurst(burst, sps, amp, toa); + return demodEdgeBurst(burst, sps, ebp); else - return demodGmskBurst(burst, sps, amp, toa); + return demodGmskBurst(burst, sps, ebp); } bool sigProcLibSetup() @@ -1890,6 +2148,9 @@ bool sigProcLibSetup() generateRACHSequence(&gRACHSequences[1], gRACHSynchSequenceTS1, 1); generateRACHSequence(&gRACHSequences[2], gRACHSynchSequenceTS2, 1); + generateSCHSequence(1); + generateDummyMidamble(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 dd03190..39c8ddd 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 }; @@ -93,6 +94,8 @@ signalVector *generateDummyBurst(int sps, int tn); void scaleVector(signalVector &x, complex scale); +signalVector *delayVector(const signalVector *in, signalVector *out, float delay); + /** Rough energy estimator. @param rxBurst A GSM burst. @@ -133,8 +136,19 @@ int detectAnyBurst(const signalVector &burst, unsigned max_toa, struct estim_burst_params *ebp); +enum class sch_detect_type { + SCH_DETECT_FULL, + SCH_DETECT_NARROW, + SCH_DETECT_BUFFER, +}; + +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, int sps, - complex amp, float toa, CorrType type); +SoftVector *demodAnyBurst(const signalVector &burst, CorrType type, + int sps, struct estim_burst_params *ebp); #endif /* SIGPROCLIB_H */ |