From dd9ba70f53ccb5a46559da747dbd94044008d20e Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sat, 12 Nov 2011 20:35:10 +0100 Subject: [PATCH] TrueBTS: first compiling L1-only BTS code (TrueBTS) This is far from being complete, but it should be a nice Layer1-only BTS code. There is no interface to higher layers yet... --- public-trunk/GSM/GSMSAPMux.h | 2 +- public-trunk/GSM/GSMTDMA.cpp | 5 + public-trunk/GSM/GSMTDMA.h | 3 + public-trunk/GSM/Makefile.am | 3 +- public-trunk/GSM/OsmoLogicalChannel.cpp | 134 +++++ public-trunk/GSM/OsmoLogicalChannel.h | 369 +++++++++++++ public-trunk/GSM/OsmoSAPMux.cpp | 37 +- public-trunk/GSM/OsmoSAPMux.h | 21 +- public-trunk/GSM/OsmoThreadMuxer.cpp | 45 ++ public-trunk/GSM/OsmoThreadMuxer.h | 82 +++ public-trunk/apps/Makefile.am | 12 +- public-trunk/apps/TrueBTS.cpp | 694 ++++++++++++++++++++++++ 12 files changed, 1398 insertions(+), 9 deletions(-) create mode 100644 public-trunk/GSM/OsmoLogicalChannel.cpp create mode 100644 public-trunk/GSM/OsmoLogicalChannel.h create mode 100644 public-trunk/GSM/OsmoThreadMuxer.cpp create mode 100644 public-trunk/GSM/OsmoThreadMuxer.h create mode 100644 public-trunk/apps/TrueBTS.cpp diff --git a/public-trunk/GSM/GSMSAPMux.h b/public-trunk/GSM/GSMSAPMux.h index b44cb12..deeb29b 100644 --- a/public-trunk/GSM/GSMSAPMux.h +++ b/public-trunk/GSM/GSMSAPMux.h @@ -66,7 +66,7 @@ class SAPMux { virtual void writeHighSide(const L2Frame& frame); virtual void writeLowSide(const L2Frame& frame); - void upstream( L2DL * wUpstream, unsigned wSAPI=0 ) + virtual void upstream( L2DL * wUpstream, unsigned wSAPI=0 ) { assert(mUpstream[wSAPI]==NULL); mUpstream[wSAPI]=wUpstream; } void downstream( L1FEC * wDownstream ) { assert(mDownstream==NULL); mDownstream=wDownstream; } diff --git a/public-trunk/GSM/GSMTDMA.cpp b/public-trunk/GSM/GSMTDMA.cpp index 217af79..ab7f8a2 100644 --- a/public-trunk/GSM/GSMTDMA.cpp +++ b/public-trunk/GSM/GSMTDMA.cpp @@ -93,6 +93,11 @@ MAKE_TDMA_MAPPING(CCCH_3,TDMA_BEACON_CCCH,true,false,0x55,true,51); // TODO -- Other CCCH subchannels 4-8 for support of C-IV. +const TDMAMapping GSM::gCCCH[4] = { + GSM::gCCCH_0Mapping, GSM::gCCCH_1Mapping, + GSM::gCCCH_2Mapping, GSM::gCCCH_3Mapping +}; + const unsigned SDCCH_4_0DFrames[] = {22,23,24,25}; MAKE_TDMA_MAPPING(SDCCH_4_0D,SDCCH_4_0,true,false,0x01,true,51); diff --git a/public-trunk/GSM/GSMTDMA.h b/public-trunk/GSM/GSMTDMA.h index dcbd71c..b70fafe 100644 --- a/public-trunk/GSM/GSMTDMA.h +++ b/public-trunk/GSM/GSMTDMA.h @@ -134,6 +134,9 @@ extern const TDMAMapping gCCCH_5Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 extern const TDMAMapping gCCCH_6Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B6 extern const TDMAMapping gCCCH_7Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B7 extern const TDMAMapping gCCCH_8Mapping; ///< GSM 05.02 Clause 7 Table 3 Line 5 B8 + +extern const TDMAMapping gCCCH[4]; + //@} /**@name SDCCH */ //@{ diff --git a/public-trunk/GSM/Makefile.am b/public-trunk/GSM/Makefile.am index 1002691..a7046b0 100644 --- a/public-trunk/GSM/Makefile.am +++ b/public-trunk/GSM/Makefile.am @@ -32,7 +32,9 @@ libGSML1_la_SOURCES = \ GSML1FEC.cpp \ GSMTDMA.cpp \ GSMTransfer.cpp \ + GSMSAPMux.cpp \ OsmoSAPMux.cpp \ + OsmoLogicalChannel.cpp \ GSMTAPDump.cpp libGSM_la_SOURCES = \ @@ -51,7 +53,6 @@ libGSM_la_SOURCES = \ GSML3RRElements.cpp \ GSML3RRMessages.cpp \ GSMLogicalChannel.cpp \ - GSMSAPMux.cpp \ PowerManager.cpp noinst_HEADERS = \ diff --git a/public-trunk/GSM/OsmoLogicalChannel.cpp b/public-trunk/GSM/OsmoLogicalChannel.cpp new file mode 100644 index 0000000..6aca974 --- /dev/null +++ b/public-trunk/GSM/OsmoLogicalChannel.cpp @@ -0,0 +1,134 @@ +/**@file Osmocom Logical Channel. */ + +/* +* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* Copyright 2011 Harald Welte +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 . + +*/ + + + +#include "OsmoLogicalChannel.h" +#include "OsmoThreadMuxer.h" + +using namespace std; +using namespace GSM; + + +ARFCNManager *OsmoTS::getARFCNmgr() +{ + TransceiverManager *trxmgr = getTRX()->getTRXmgr(); + return trxmgr->ARFCN(getTSnr()); +} + +OsmoTS::OsmoTS(OsmoTRX &trx, unsigned int ts_nr, unsigned comb) +{ + assert(ts_nr < 8); + mComb = comb; + mTSnr = ts_nr; + mTRX = &trx; + mNLchan = 0; + + TransceiverManager *trxmgr = trx.getTRXmgr(); + ARFCNManager *radio = trxmgr->ARFCN(ts_nr); + radio->setSlot(ts_nr, comb); +} + + +void OsmoLogicalChannel::open() +{ + LOG(INFO); + if (mSACCHL1) mSACCHL1->open(); + if (mL1) mL1->open(); +} + + +void OsmoLogicalChannel::connect() +{ + mMux.downstream(mL1); + if (mL1) mL1->upstream(&mMux); + //mMux.upstream(mL2[s],s); + //if (mL2[s]) mL2[s]->downstream(&mMux); +} + +void OsmoLogicalChannel::writeLowSide(const L2Frame& frame) +{ + mTM->writeLowSide(frame, this); +} + +ostream& GSM::operator<<(ostream& os, const OsmoLogicalChannel& lchan) +{ + unsigned int trx_nr, ts_nr, ss_nr; + trx_nr = lchan.TS()->getTRX()->getTN(); + ts_nr = lchan.TS()->getTSnr(); + ss_nr = lchan.SSnr(); + + os << "(" << trx_nr << "," << ts_nr << "," << ss_nr << ")"; +} + + +void OsmoLogicalChannel::downstream(ARFCNManager* radio) +{ + assert(mL1); + mL1->downstream(radio); + if (mSACCHL1) mSACCHL1->downstream(radio); +} + +OsmoCCCHLchan::OsmoCCCHLchan(OsmoTS *osmo_ts, unsigned int ss_nr) + :OsmoNDCCHLogicalChannel(osmo_ts, ss_nr) +{ + mL1 = new CCCHL1FEC(gCCCH[ss_nr]); + connect(); +} + + +OsmoSDCCHLchan::OsmoSDCCHLchan(OsmoTS *osmo_ts, unsigned int ss_nr) + :OsmoLogicalChannel(osmo_ts, ss_nr) +{ + unsigned int ts_nr = osmo_ts->getTSnr(); + const CompleteMapping *wMapping = NULL; + + switch (osmo_ts->getComb()) { + case 5: + wMapping = &gSDCCH4[ss_nr]; + break; + case 7: + wMapping = &gSDCCH8[ss_nr]; + break; + default: + assert(0); + } + mL1 = new SDCCHL1FEC(ts_nr, wMapping->LCH()); + connect(); +} + +OsmoTCHFACCHLchan::OsmoTCHFACCHLchan(OsmoTS *osmo_ts, unsigned int ss_nr) + :OsmoLogicalChannel(osmo_ts, ss_nr) +{ + unsigned int ts_nr = osmo_ts->getTSnr(); + mL1 = new TCHFACCHL1FEC(ts_nr, gTCHF_T[ts_nr].LCH()); + connect(); +} + + +// vim: ts=4 sw=4 + diff --git a/public-trunk/GSM/OsmoLogicalChannel.h b/public-trunk/GSM/OsmoLogicalChannel.h new file mode 100644 index 0000000..7e258c9 --- /dev/null +++ b/public-trunk/GSM/OsmoLogicalChannel.h @@ -0,0 +1,369 @@ +/**@file Logical Channel. */ + +/* +* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 . + +*/ + + + + +#ifndef OSMOLOGICALCHANNEL_H +#define OSMOLOGICALCHANNEL_H + +#include +#include +#include + + +#include "GSML1FEC.h" +#include "GSMSAPMux.h" +#include "GSMTDMA.h" +#include "OsmoSAPMux.h" + +#include +#include + +class ARFCNManager; + + + +namespace GSM { + +class OsmoTRX; +class OsmoThreadMuxer; +class OsmoLogicalChannel; + +/* virtual class from which we derive timeslots */ +class OsmoTS { +protected: + OsmoTRX *mTRX; + unsigned int mTSnr; + OsmoLogicalChannel *mLchan[8]; + unsigned int mNLchan; + unsigned int mComb; +public: + OsmoTS(OsmoTRX &trx, unsigned int ts_nr, unsigned comb); + unsigned int getTSnr() const { return mTSnr; } + unsigned int getComb() { return mComb; } + const OsmoTRX *getTRX() const { return mTRX; } + ARFCNManager *getARFCNmgr(); + OsmoLogicalChannel *getLchan(unsigned int nr) { + assert(nr < 8); + if (nr < mNLchan) + return mLchan[nr]; + else + return NULL; + } +}; + +/* One TRX (8TS) */ +class OsmoTRX { +protected: + TransceiverManager *mTRXmgr; + OsmoThreadMuxer *mThreadMux; + unsigned int mTN; + OsmoTS *mTS[8]; +public: + OsmoTRX(TransceiverManager &TRXmgr, unsigned int trx_nr) { + mTRXmgr = &TRXmgr; + mTN = trx_nr; + } + + TransceiverManager *getTRXmgr() const { return mTRXmgr; } + OsmoThreadMuxer *getThreadMux() const { return mThreadMux; } + unsigned int getTN() const { return mTN; } + + OsmoTS *getTS(unsigned int nr) { + assert(nr < 8); + return mTS[nr]; + } + + OsmoLogicalChannel *getLchan(unsigned int ts_nr, unsigned int lchan_nr) { + OsmoTS *ts = getTS(ts_nr); + return ts->getLchan(lchan_nr); + } +}; + +/** + A complete logical channel. + Includes processors for L1, L2, L3, as needed. + The layered structure of GSM is defined in GSM 04.01 7, as well as many other places. + The concept of the logical channel and the channel types are defined in GSM 04.03. + This is virtual class; specific channel types are subclasses. +*/ +//class OsmoLogicalChannel : public virtual LogicalChannelCommon { +class OsmoLogicalChannel { + +protected: + + L1FEC *mL1; ///< L1 forward error correction + OsmoSAPMux mMux; ///< service access point multiplex + OsmoThreadMuxer *mTM; + unsigned int mSS; // sub-slot (logical channel within TS) + OsmoTS *mTS; + SACCHL1FEC *mSACCHL1; ///< The associated SACCH, if any. + +public: + + /** + Blank initializer just nulls the pointers. + Specific sub-class initializers allocate new components as needed. + */ + OsmoLogicalChannel(OsmoTS *osmo_ts, unsigned int ss_nr) + :mSACCHL1(NULL) + { + mTS = osmo_ts; + mSS = ss_nr; + /* resolve the thread muxer */ + const OsmoTRX *otrx = osmo_ts->getTRX(); + mTM = otrx->getThreadMux(); + } + + /** Connect an ARFCN manager to link L1FEC to the radio. */ + void downstream(ARFCNManager* radio); + + /**@name Accessors. */ + //@{ + SACCHL1FEC* SACCH() { return mSACCHL1; } + const SACCHL1FEC* SACCH() const { return mSACCHL1; } + const OsmoTS* TS() const { return mTS; } + unsigned int SSnr() const { return mSS; } + //@} + + + /**@name Pass-throughs. */ + //@{ + + /** Set L1 physical parameters from a RACH or pre-exsting channel. */ + virtual void setPhy(float wRSSI, float wTimingError) { + assert(mSACCHL1); + mSACCHL1->setPhy(wRSSI, wTimingError); + } + + /* Set L1 physical parameters from an existing logical channel. */ + virtual void setPhy(const OsmoLogicalChannel& other) { + assert(mSACCHL1); + mSACCHL1->setPhy(*other.SACCH()); + } + + //@} + + + /**@name L3 interfaces */ + //@{ + // + virtual void writeLowSide(const L2Frame& frame); + + //@} // passthrough + + /**@name Channel stats from the physical layer */ + //@{ + /** RSSI wrt full scale. */ + virtual float RSSI() const { return mSACCHL1->RSSI(); } + /** Uplink timing error. */ + virtual float timingError() const { return mSACCHL1->timingError(); } + /** Actual MS uplink power. */ + virtual int actualMSPower() const { return mSACCHL1->actualMSPower(); } + /** Actual MS uplink timing advance. */ + virtual int actualMSTiming() const { return mSACCHL1->actualMSTiming(); } + //@} + + /** Return the channel type. */ + virtual ChannelType type() const =0; + + /** + Make the channel ready for a new transaction. + The channel is closed with primitives from L3. + */ + virtual void open(); + + protected: + + /** + Make the normal inter-layer connections. + Should be called from inside the constructor after + the channel components are created. + */ + virtual void connect(); + + friend std::ostream& operator<<(std::ostream& os, const OsmoLogicalChannel& lchan); +}; + +//std::ostream& GSM::operator<<(std::ostream& os, const OsmoLogicalChannel& lchan); + +/** + Standalone dedicated control channel. + GSM 04.06 4.1.3: "A dedicated control channel (DCCH) is a point-to-point + bi-directional or uni-directional control channel. ... A SDCCH (Stand-alone + DCCH) is a bi-directional DCCH whose allocation is not linked to the + allocation of a TCH. The bit rate of a SDCCH is 598/765 kbit/s. +" +*/ +class OsmoSDCCHLchan : public OsmoLogicalChannel { + + public: + + OsmoSDCCHLchan(OsmoTS *osmo_ts, unsigned int ss_nr); + + ChannelType type() const { return SDCCHType; } +}; + + +/** + Logical channel for NDCCHs that use Bbis format and a pseudolength. + This is a virtual base class this is extended for CCCH & BCCH. + See GSM 04.06 4.1.1, 4.1.3. +*/ +class OsmoNDCCHLogicalChannel : public OsmoLogicalChannel { + + public: + OsmoNDCCHLogicalChannel(OsmoTS *osmo_ts, unsigned int ss_nr) : + OsmoLogicalChannel(osmo_ts, ss_nr) { }; + +}; + + +/** + Common control channel. + The "uplink" component of the CCCH is the RACH. + See GSM 04.03 4.1.2: "A common control channel is a point-to-multipoint + bi-directional control channel. Common control channels are physically + sub-divided into the common control channel (CCCH), the packet common control + channel (PCCCH), and the Compact packet common control channel (CPCCCH)." +*/ +class OsmoCCCHLchan : public OsmoNDCCHLogicalChannel { + public: + OsmoCCCHLchan(OsmoTS *osmo_ts, unsigned int ss_nr); + + ChannelType type() const { return CCCHType; } +}; + +class OsmoTCHFACCHLchan : public OsmoLogicalChannel { + + protected: + + TCHFACCHL1FEC * mTCHL1; + + public: + + OsmoTCHFACCHLchan(OsmoTS *osmo_ts, unsigned int ss_nr); + + ChannelType type() const { return FACCHType; } + + void sendTCH(const unsigned char* frame) + { assert(mTCHL1); mTCHL1->sendTCH(frame); } + + unsigned char* recvTCH() + { assert(mTCHL1); return mTCHL1->recvTCH(); } + + unsigned queueSize() const + { assert(mTCHL1); return mTCHL1->queueSize(); } + + bool radioFailure() const + { assert(mTCHL1); return mTCHL1->radioFailure(); } +}; + +//@} + +/* timeslot in Combination I (TCH/F) */ +class OsmoComb1TS : public OsmoTS { +protected: +public: + OsmoComb1TS(OsmoTRX &trx, unsigned int ts_nr) :OsmoTS(trx, ts_nr, 1) { + ARFCNManager* radio = getARFCNmgr(); + /* create logical channel */ + OsmoTCHFACCHLchan * chan = new OsmoTCHFACCHLchan(this, 0); + chan->downstream(radio); + chan->open(); + mLchan[0] = chan; + mNLchan = 1; + } +}; + +/* timeslot in Combination 5 (FCCH, SCH, CCCH, BCCH and 4*SDCCH/4) */ +class OsmoComb5TS : public OsmoTS { +public: + OsmoComb5TS(OsmoTRX &trx, unsigned int tn) :OsmoTS(trx, tn, 5) { + ARFCNManager* radio = getARFCNmgr(); + + SCHL1FEC SCH; + SCH.downstream(radio); + SCH.open(); + + FCCHL1FEC FCCH; + FCCH.downstream(radio); + SCH.open(); + + RACHL1FEC RACH(gRACHC5Mapping); + RACH.downstream(radio); + RACH.open(); + + OsmoCCCHLchan CCCH0(this, 0); + CCCH0.downstream(radio); + CCCH0.open(); + + OsmoCCCHLchan CCCH1(this, 1); + CCCH1.downstream(radio); + CCCH1.open(); + + OsmoCCCHLchan CCCH2(this, 2); + CCCH2.downstream(radio); + CCCH2.open(); + + for (int i = 0; i < 4; i++) { + /* create logical channel */ + OsmoSDCCHLchan * chan = new OsmoSDCCHLchan(this, i); + chan->downstream(radio); + chan->open(); + mLchan[i] = chan; + mNLchan++; + + } + } +}; + +/* timeslot in Combination 7 (SDCCH/8) */ +class OsmoComb7TS : public OsmoTS { +protected: +public: + OsmoComb7TS(OsmoTRX &trx, unsigned int tn) :OsmoTS(trx, tn, 7) { + for (unsigned int i = 0; i < 8; i++) { + ARFCNManager* radio = getARFCNmgr(); + /* create logical channel */ + OsmoSDCCHLchan *chan = new OsmoSDCCHLchan(this, i); + chan->downstream(radio); + chan->open(); + mLchan[i] = chan; + mNLchan++; + } + } +}; + + + +}; // GSM + +#endif + + +// vim: ts=4 sw=4 diff --git a/public-trunk/GSM/OsmoSAPMux.cpp b/public-trunk/GSM/OsmoSAPMux.cpp index 8eb307b..eb6009d 100644 --- a/public-trunk/GSM/OsmoSAPMux.cpp +++ b/public-trunk/GSM/OsmoSAPMux.cpp @@ -27,6 +27,7 @@ #include "OsmoSAPMux.h" #include "GSMTransfer.h" #include "GSML1FEC.h" +#include "OsmoLogicalChannel.h" #include @@ -36,15 +37,45 @@ using namespace GSM; void OsmoSAPMux::writeHighSide(const L2Frame& frame) { - // The SAP may or may not be present, depending on the channel type. OBJLOG(DEEPDEBUG) << "OsmoSAPMux::writeHighSide " << frame; + /* put it into the top side of the L2 FIFO */ + mL2Q.write(new L2Frame(frame)); } - - void OsmoSAPMux::writeLowSide(const L2Frame& frame) { OBJLOG(DEEPDEBUG) << "OsmoSAPMux::writeLowSide SAP" << frame.SAPI() << " " << frame; + assert(mLchan); + /* simply pass it right through to the OsmoThreadMux */ + mLchan->writeLowSide(frame); +} + +void OsmoSAPMux::dispatch() +{ + L2Frame *frame; + + assert(mDownstream); + + /* blocking read from FIFO */ + frame = mL2Q.read(); + + /* blocking write to L1Encoder */ + mLock.lock(); + mDownstream->writeHighSide(*frame); + mLock.unlock(); +} + +void GSM::OsmoSAPRoutine( OsmoSAPMux *osm ) +{ + while (1) { + osm->dispatch(); + } +} + +void OsmoSAPMux::start() +{ + OBJLOG(DEBUG) << "OsmoSAPMux"; + mQueueThread.start((void*(*)(void*))OsmoSAPRoutine,(void *)this); } // vim: ts=4 sw=4 diff --git a/public-trunk/GSM/OsmoSAPMux.h b/public-trunk/GSM/OsmoSAPMux.h index 5a16f05..240f1f1 100644 --- a/public-trunk/GSM/OsmoSAPMux.h +++ b/public-trunk/GSM/OsmoSAPMux.h @@ -35,6 +35,8 @@ namespace GSM { +class OsmoLogicalChannel; + /** A SAPMux is a multipexer the connects a single L1 to multiple L2s. A "service access point" in GSM/ISDN is analogous to port number in IP. @@ -44,10 +46,13 @@ namespace GSM { class OsmoSAPMux : public SAPMux { protected: + OsmoLogicalChannel *mLchan; + L2FrameFIFO mL2Q; + Thread mQueueThread; public: - OsmoSAPMux(){ + OsmoSAPMux() { } virtual ~OsmoSAPMux() {} @@ -55,13 +60,23 @@ class OsmoSAPMux : public SAPMux { virtual void writeHighSide(const L2Frame& frame); virtual void writeLowSide(const L2Frame& frame); + void upstream(OsmoLogicalChannel *lchan) { + assert(mLchan == NULL); + mLchan = lchan; + } + void upstream( L2DL * wUpstream, unsigned wSAPI=0 ) { } - void downstream( L1FEC * wDownstream ) - { } + /* use downstream() of SAPMux */ + /* actual dispatch routine, called in endless loop */ + void dispatch(); + + /* start the dispatch thread */ + void start(); }; +void OsmoSAPRoutine( OsmoSAPMux *osm ); } // namespace GSM diff --git a/public-trunk/GSM/OsmoThreadMuxer.cpp b/public-trunk/GSM/OsmoThreadMuxer.cpp new file mode 100644 index 0000000..45e407d --- /dev/null +++ b/public-trunk/GSM/OsmoThreadMuxer.cpp @@ -0,0 +1,45 @@ +/* +* Copyright 2011 Harald Welte +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 . + +*/ + + +#include "GSMTransfer.h" +#include "OsmoLogicalChannel.h" +#include "OsmoThreadMuxer.h" +#include + + + +using namespace std; +using namespace GSM; + + +OsmoThreadMuxer::writeLowSide(const L2Frame& frame, + struct OsmoLogicalChannel *lchan) +{ + OBJLOG(DEEPDEBUG) << "OsmoThreadMuxer::writeLowSide" << lchan << " " << frame; + /* resolve SAPI, SS, TS, TRX numbers */ + /* build primitive that we can put into the up-queue */ +} + +// vim: ts=4 sw=4 diff --git a/public-trunk/GSM/OsmoThreadMuxer.h b/public-trunk/GSM/OsmoThreadMuxer.h new file mode 100644 index 0000000..7eb1528 --- /dev/null +++ b/public-trunk/GSM/OsmoThreadMuxer.h @@ -0,0 +1,82 @@ +/* +* Copyright 2011 Harald Welte +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 . + +*/ + +#ifndef OsmoThreadMuxer_H +#define OsmoThreadMuxer_H + +#include "OsmoLogicalChannel.h" +#include + +namespace GSM { + + +/* The idea of this monster is to provide an interface between the + * heavily multi-threaded OpenBTS architecture and the single-threaded + * osmo-bts architecture. + * + * In the Uplink Rx path, L1Decoder calls OsmoSAPMux:writeLowSide, + * which directly passes the call through to * OsmoThreadMuxer:writeLowSide + * + * At this point, the L2Frame needs to be converted from unpacked bits + * to packed bits, and wrapped with some layer1 primitive header. Next, + * it is enqueued into a FIFO leading towards osmo-bts. Whenever that + * FIFO has data to be written, we signal this via the sock_fd. + * (socketpair). The other fd ends up in the select() loop of osmo-bts. + * + * In the Downling Tx path, the OsmoThreadMuxer thread FIXME + */ +class OsmoThreadMuxer { + +protected: + int mSockFd[2]; + OsmoTRX *mTRX[1]; + unsigned int mNumTRX; + +public: + OsmoThreadMuxer() { + int rc; + + rc = socketpair(AF_UNIX, SOCK_DGRAM, 0, mSockFd); + } + + int getUserFd() { + return mSockFd[1]; + } + + int addTRX(TransceiverManager &trx_mgr, unsigned int trx_nr) { + /* for now we only support a single TRX */ + assert(mNumTRX == 0); + OsmoTRX *otrx = new OsmoTRX(trx_mgr, trx_nr); + mTRX[mNumTRX++] = otrx; + } + + /* receive frame synchronously from L1Decoder->OsmoSAPMux and + * euqneue it towards osmo-bts */ + virtual void writeLowSide(const L2Frame& frame, + OsmoLogicalChannel *lchan); +}; + +}; // GSM + +#endif /* OsmoThreadMuxer_H */ diff --git a/public-trunk/apps/Makefile.am b/public-trunk/apps/Makefile.am index 16c3bcc..0de9855 100644 --- a/public-trunk/apps/Makefile.am +++ b/public-trunk/apps/Makefile.am @@ -25,7 +25,8 @@ AM_CXXFLAGS = -Wall -g -pthread noinst_PROGRAMS = \ OpenBTS \ - OpenBTScli + OpenBTScli \ + TrueBTS OpenBTS_SOURCES = OpenBTS.cpp OpenBTS_LDADD = \ @@ -49,6 +50,15 @@ OpenBTScli_LDADD = \ $(COMMON_LA) \ $(CLI_LA) +TrueBTS_SOURCES = TrueBTS.cpp +TrueBTS_LDADD = \ + $(GLOBALS_LA) \ + $(COMMON_LA) \ + $(GSML1_LA) \ + $(TRX_LA) \ + $(COMMON_LA) \ + $(CLI_LA) + EXTRA_DIST = \ OpenBTS.config.example \ diff --git a/public-trunk/apps/TrueBTS.cpp b/public-trunk/apps/TrueBTS.cpp new file mode 100644 index 0000000..ece7c86 --- /dev/null +++ b/public-trunk/apps/TrueBTS.cpp @@ -0,0 +1,694 @@ +/* +* Copyright 2008, 2009, 2010 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 . + +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace std; +using namespace GSM; +using namespace CommandLine; + +static int daemonize(std::string &lockfile, int &lfp); +static int forkLoop(); + +class DaemonInitializer +{ +public: + DaemonInitializer(bool doDaemonize) + : mLockFileFD(-1) + { + // Start in daemon mode? + if (doDaemonize) + if (daemonize(mLockFileName, mLockFileFD) != EXIT_SUCCESS) + exit(EXIT_FAILURE); + } + + ~DaemonInitializer() + { + if (mLockFileFD >= 0) close(mLockFileFD); + if (mLockFileName.size() > 0) { + if (unlink(mLockFileName.data()) == 0) { + LOG(INFO) << "Deleted lock file " << mLockFileName; + } else { + LOG(INFO) << "Error while deleting lock file " << mLockFileName + << " code=" << errno << ": " << strerror(errno); + } + } + } + +protected: + std::string mLockFileName; + int mLockFileFD; +}; + +class Restarter +{ +public: + Restarter(bool restartOnCrash) + { + if (restartOnCrash) + if (forkLoop() != EXIT_SUCCESS) + exit(EXIT_FAILURE); + } +}; + +/// Load configuration from a file. +ConfigurationTable gConfig("TrueBTS.config"); +/// Initialize Logger form the config. +static LogInitializer sgLogInitializer; +/// Fork daemon if needed. +static DaemonInitializer sgDaemonInitializer(gConfig.defines("Server.Daemonize")); +/// Fork a child and restart it if it crash. Kind of failsafe. +static Restarter sgRestarter(gConfig.defines("Server.RestartOnCrash")); + + +// All of the other globals that rely on the global configuration file need to +// be declared here. + +/// Configure the BTS object based on the config file. +/// So don't create this until AFTER loading the config file. +GSMConfigL1 &gBTSL1; + +/// Our interface to the software-defined radio. +TransceiverManager gTRX(1, gConfig.getStr("TRX.IP"), gConfig.getNum("TRX.Port")); + +/// Pointer to the server socket if we run remote CLI. +static ConnectionServerSocket *sgCLIServerSock = NULL; + +/// We store Transceiver PID if we start it. +static pid_t sgTransceiverPid = 0; +static int sgTransceiverPidFileFd = -1; +static std::string sgTransceiverPidFile; + +void Control::AccessGrantResponder(unsigned requestReference, const GSM::Time& when, + float RSSI, float timingError) +{ + /* Do nothing */ +} + +/** Function to shutdown the process when something wrong happens. */ +void shutdownOpenbts() +{ + kill(SIGTERM, getpid()); +} + +static int openPidFile(const std::string &lockfile) +{ + int lfp = open(lockfile.data(), O_RDWR|O_CREAT, 0640); + if (lfp < 0) { + LOG(ERROR) << "Unable to create PID file " << lockfile << ", code=" + << errno << " (" << strerror(errno) << ")"; + } else { + LOG(INFO) << "Created PID file " << lockfile; + } + return lfp; +} + +static int lockPidFile(const std::string &lockfile, int lfp, bool block=false) +{ + if (lockf(lfp, block?F_LOCK:F_TLOCK,0) < 0) { + LOG(ERROR) << "Unable to lock PID file " << lockfile << ", code=" + << errno << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +static int writePidFile(const std::string &lockfile, int lfp, int pid) +{ + // Clear old file content first + if (ftruncate(lfp, 0) < 0) { + LOG(ERROR) << "Unable to clear PID file " << lockfile << ", code=" + << errno << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } + + // Write PID + char tempBuf[64]; + snprintf(tempBuf, sizeof(tempBuf), "%d\n", pid); + ssize_t tempDataLen = strlen(tempBuf); + lseek(lfp, 0, SEEK_SET); + if (write(lfp, tempBuf, tempDataLen) != tempDataLen) { + LOG(ERROR) << "Unable to write PID to file " << lockfile << ", code=" + << errno << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +static int readPidFile(const std::string &lockfile, int lfp, int &pid) +{ + char tempBuf[64]; + lseek(lfp, 0, SEEK_SET); + int bytesRead = read(lfp, tempBuf, sizeof(tempBuf)); + if (bytesRead <= 0) { + LOG(ERROR) << "Unable to read PID from file " << lockfile << ", code=" + << errno << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } + tempBuf[bytesRead=0); + if (sgTransceiverPid==0) { + // Pid==0 means this is the process that starts the transceiver. + execl(TRXPath,"transceiver",TRXLogLevel,TRXLogFileName,NULL); + LOG(ERROR) << "cannot start transceiver"; + _exit(0); + } + // Now we can finally write transceiver PID to the file. + if (writePidFile(sgTransceiverPidFile, sgTransceiverPidFileFd, sgTransceiverPid) != EXIT_SUCCESS) return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +static void serverCleanup() +{ + if (sgTransceiverPid) { + kill(sgTransceiverPid, SIGTERM); + if (sgTransceiverPidFileFd >= 0) { + close(sgTransceiverPidFileFd); + } + if (sgTransceiverPidFile.size() > 0) { + if (unlink(sgTransceiverPidFile.data()) == 0) { + LOG(INFO) << "Deleted lock file " << sgTransceiverPidFile; + } else { + LOG(INFO) << "Error while deleting lock file " << sgTransceiverPidFile + << " code=" << errno << ": " << strerror(errno); + } + } + } +} + +static void exitCLI() +{ + if (sgCLIServerSock == NULL) { + serverCleanup(); + _exit(EXIT_SUCCESS); + } else { + // Closing server sock + sgCLIServerSock->close(); + sgCLIServerSock = NULL; + } + + // Closing server standard input to shutdown local CLI + // Following functions are not async-signal-safe, but I don't have + // better idea how to do this. + cin.setstate(ios::eofbit); +// cin.putback('\n'); + fclose(stdin); +} + +static void daemonChildHandler(int signum) +{ + LOG(INFO) << "Handling signal " << signum; + switch(signum) { + case SIGALRM: + // alarm() fired. + exit(EXIT_FAILURE); + break; + case SIGUSR1: + //Child sent us a signal. Good sign! + exit(EXIT_SUCCESS); + break; + case SIGCHLD: + // Child has died + exit(EXIT_FAILURE); + break; + } +} + +static int daemonize(std::string &lockfile, int &lfp) +{ + // Already a daemon + if ( getppid() == 1 ) return EXIT_SUCCESS; + + // Sanity checks + if (strcasecmp(gConfig.getStr("CLI.Type"),"Local") == 0) { + LOG(ERROR) << "OpenBTS runs in daemon mode, but CLI is set to Local!"; + return EXIT_FAILURE; + } + if (!gConfig.defines("Server.WritePID")) { + LOG(ERROR) << "OpenBTS runs in daemon mode, but Server.WritePID is not set in config!"; + return EXIT_FAILURE; + } + + // According to the Filesystem Hierarchy Standard 5.13.2: + // "The naming convention for PID files is .pid." + // The same standard specifies that PID files should be placed + // in /var/run, but we make this configurable. + lockfile = gConfig.getStr("Server.WritePID"); + + // Create the PID file as the current user + if ((lfp=openPidFile(lockfile)) < 0) return EXIT_FAILURE; + + // Drop user if there is one, and we were run as root +/* if ( getuid() == 0 || geteuid() == 0 ) { + struct passwd *pw = getpwnam(RUN_AS_USER); + if ( pw ) { + syslog( LOG_NOTICE, "setting user to " RUN_AS_USER ); + setuid( pw->pw_uid ); + } + } +*/ + + // Trap signals that we expect to receive + signal(SIGCHLD, daemonChildHandler); + signal(SIGUSR1, daemonChildHandler); + signal(SIGALRM, daemonChildHandler); + + // Fork off the parent process + pid_t pid = fork(); + if (pid < 0) { + LOG(ERROR) << "Unable to fork daemon, code=" << errno + << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } + // If we got a good PID, then we can exit the parent process. + if (pid > 0) { + // Wait for confirmation from the child via SIGUSR1 or SIGCHLD. + LOG(INFO) << "Forked child process with PID " << pid; + // Some recommend to add timeout here too (it will signal SIGALRM), + // but I don't think it's a good idea if we start on a slow system. + // Or may be we should make timeout value configurable and set it + // a big enough value. +// alarm(2); + // pause() should not return. + pause(); + LOG(ERROR) << "Executing code after pause()!"; + return EXIT_FAILURE; + } + + // Now lock our PID file and write our PID to it + if (lockPidFile(lockfile, lfp) != EXIT_SUCCESS) return EXIT_FAILURE; + if (writePidFile(lockfile, lfp, getpid()) != EXIT_SUCCESS) return EXIT_FAILURE; + + // At this point we are executing as the child process + pid_t parent = getppid(); + + // Return signals to default handlers + signal(SIGCHLD, SIG_DFL); + signal(SIGUSR1, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + // Change the file mode mask + // This will restrict file creation mode to 750 (complement of 027). + umask(gConfig.getNum("Server.umask")); + + // Create a new SID for the child process + pid_t sid = setsid(); + if (sid < 0) { + LOG(ERROR) << "Unable to create a new session, code=" << errno + << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } + + // Change the current working directory. This prevents the current + // directory from being locked; hence not being able to remove it. + if (gConfig.defines("Server.ChdirToRoot")) { + if (chdir("/") < 0) { + LOG(ERROR) << "Unable to change directory to %s, code" << errno + << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } else { + LOG(INFO) << "Changed current directory to \"/\""; + } + } + + // Redirect standard files to /dev/null + if (freopen( "/dev/null", "r", stdin) == NULL) + LOG(WARN) << "Error redirecting stdin to /dev/null"; + if (freopen( "/dev/null", "w", stdout) == NULL) + LOG(WARN) << "Error redirecting stdout to /dev/null"; + if (freopen( "/dev/null", "w", stderr) == NULL) + LOG(WARN) << "Error redirecting stderr to /dev/null"; + + // Tell the parent process that we are okay + kill(parent, SIGUSR1); + + return EXIT_SUCCESS; +} + +static int forkLoop() +{ + bool shouldExit = false; + sigset_t chldSignalSet; + sigemptyset(&chldSignalSet); + sigaddset(&chldSignalSet, SIGCHLD); + sigaddset(&chldSignalSet, SIGTERM); + sigaddset(&chldSignalSet, SIGINT); + sigaddset(&chldSignalSet, SIGKILL); + + // Block signals to avoid race condition. + // It will be delivered to us in sigwait() when we are ready to handle it. + sigprocmask(SIG_BLOCK, &chldSignalSet, NULL); + + while (1) { + // Fork off the parent process + pid_t pid = fork(); + if (pid < 0) { + // fork() failed. + LOG(ERROR) << "Unable to fork child, code=" << errno + << " (" << strerror(errno) << ")"; + return EXIT_FAILURE; + } else if (pid > 0) { + // Parent process + // Wait for child process to exit (SIGCHLD). + LOG(INFO) << "Forked child process with PID " << pid; + int signum = -1; + while (signum != SIGCHLD) { + sigwait(&chldSignalSet, &signum); + switch(signum) { + case SIGCHLD: + LOG(ERROR) << "Child with PID " << pid << " died."; + if (shouldExit) exit(EXIT_SUCCESS); + break; + case SIGTERM: + case SIGINT: + case SIGKILL: + // Forward signal to the child. + kill(pid, signum); + // We will exit child exits and send us SIGCHLD. + shouldExit = true; + } + } + } else { + // Child process + // Unblock signals we blocked. + sigprocmask(SIG_UNBLOCK, &chldSignalSet, NULL); + return EXIT_SUCCESS; + } + } + + return EXIT_SUCCESS; +} + +static void signalHandler(int sig) +{ + COUT("Handling signal " << sig); + LOG(INFO) << "Handling signal " << sig; + switch(sig){ + case SIGHUP: + // re-read the config + // TODO:: + break; + case SIGTERM: + case SIGINT: + // finalize the server + exitCLI(); + break; + default: + break; + } +} + +int main(int argc, char *argv[]) +{ + srandom(time(NULL)); + + // Catch signal to re-read config + if (signal(SIGHUP, signalHandler) == SIG_ERR) { + cerr << "Error while setting handler for SIGHUP."; + return EXIT_FAILURE; + } + // Catch signal to shutdown gracefully + if (signal(SIGTERM, signalHandler) == SIG_ERR) { + cerr << "Error while setting handler for SIGTERM."; + return EXIT_FAILURE; + } + // Catch Ctrl-C signal + if (signal(SIGINT, signalHandler) == SIG_ERR) { + cerr << "Error while setting handler for SIGINT."; + return EXIT_FAILURE; + } + // Various TTY signals + // We don't really care about return values of these. + signal(SIGTSTP,SIG_IGN); + signal(SIGTTOU,SIG_IGN); + signal(SIGTTIN,SIG_IGN); + + cout << endl << endl << gOpenBTSWelcome << endl; + + try { + + cout << endl << "Starting the system..." << endl; + +#if 0 + if (gConfig.defines("Control.TMSITable.SavePath")) { + gTMSITable.load(gConfig.getStr("Control.TMSITable.SavePath")); + } +#endif + + LOG(ALARM) << "TrueBTS starting, ver " << VERSION << " build date " << __DATE__; + + startTransceiver(); + + // Start the transceiver interface. + // Sleep long enough for the USRP to bootload. + sleep(5); + gTRX.start(); + + // Set up the interface to the radio. + // Get a handle to the C0 transceiver interface. + ARFCNManager* radio = gTRX.ARFCN(0); + + // Tuning. + // Make sure its off for tuning. + radio->powerOff(); + // Set TSC same as BCC everywhere. + radio->setTSC(gBTSL1.BCC()); + // Tune. + radio->tune(gConfig.getNum("GSM.ARFCN")); + + // Turn on and power up. + radio->powerOn(); + radio->setPower(gConfig.getNum("GSM.PowerManager.MinAttenDB")); + + // Set maximum expected delay spread. + radio->setMaxDelay(gConfig.getNum("GSM.MaxExpectedDelaySpread")); + + // Set Receiver Gain + radio->setRxGain(gConfig.getNum("GSM.RxGain")); + + OsmoTRX TRX0(gTRX, 0); + + OsmoComb5TS TS0(TRX0, 0); + OsmoComb1TS TS1(TRX0, 1); + +#if 0 + // C-V on C0T0 + radio->setSlot(0,5); + // SCH + SCHL1FEC SCH; + SCH.downstream(radio); + SCH.open(); + // FCCH + FCCHL1FEC FCCH; + FCCH.downstream(radio); + FCCH.open(); + + // BCCH + BCCHL1FEC BCCH; + BCCH.downstream(radio); + BCCH.open(); + // RACH + RACHL1FEC RACH(gRACHC5Mapping); + RACH.downstream(radio); + RACH.open(); + // CCCHs + CCCHLogicalChannel CCCH0(gCCCH_0Mapping); + CCCH0.downstream(radio); + CCCH0.open(); + CCCHLogicalChannel CCCH1(gCCCH_1Mapping); + CCCH1.downstream(radio); + CCCH1.open(); + CCCHLogicalChannel CCCH2(gCCCH_2Mapping); + CCCH2.downstream(radio); + CCCH2.open(); + // use CCCHs as AGCHs + gBTS.addAGCH(&CCCH0); + gBTS.addAGCH(&CCCH1); + gBTS.addAGCH(&CCCH2); + + // C-V C0T0 SDCCHs + SDCCHLogicalChannel C0T0SDCCH[4] = { + SDCCHLogicalChannel(0,gSDCCH_4_0), + SDCCHLogicalChannel(0,gSDCCH_4_1), + SDCCHLogicalChannel(0,gSDCCH_4_2), + SDCCHLogicalChannel(0,gSDCCH_4_3), + }; + Thread C0T0SDCCHControlThread[4]; + for (int i=0; i<4; i++) { + C0T0SDCCH[i].downstream(radio); + C0T0SDCCHControlThread[i].start((void*(*)(void*))Control::DCCHDispatcher,&C0T0SDCCH[i]); + C0T0SDCCH[i].open(); + gBTS.addSDCCH(&C0T0SDCCH[i]); + } + + // Count configured slots. + unsigned sCount = 1; + + bool halfDuplex = gConfig.defines("GSM.HalfDuplex"); + if (halfDuplex) { LOG(NOTICE) << "Configuring for half-duplex operation." ; } + else { LOG(NOTICE) << "Configuring for full-duplex operation."; } + + if (halfDuplex) sCount++; + + // Create C-VII slots. + for (int i=0; i