laforge
/
openbts-osmo
Archived
1
0
Fork 0

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...
This commit is contained in:
Harald Welte 2011-11-12 20:35:10 +01:00
parent 1c41c4e0ae
commit dd9ba70f53
12 changed files with 1398 additions and 9 deletions

View File

@ -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; }

View File

@ -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);

View File

@ -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 */
//@{

View File

@ -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 = \

View File

@ -0,0 +1,134 @@
/**@file Osmocom Logical Channel. */
/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2011 Harald Welte <laforge@gnumonks.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef OSMOLOGICALCHANNEL_H
#define OSMOLOGICALCHANNEL_H
#include <sys/types.h>
#include <pthread.h>
#include <iostream>
#include "GSML1FEC.h"
#include "GSMSAPMux.h"
#include "GSMTDMA.h"
#include "OsmoSAPMux.h"
#include <TRXManager.h>
#include <Logger.h>
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

View File

@ -27,6 +27,7 @@
#include "OsmoSAPMux.h"
#include "GSMTransfer.h"
#include "GSML1FEC.h"
#include "OsmoLogicalChannel.h"
#include <Logger.h>
@ -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

View File

@ -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

View File

@ -0,0 +1,45 @@
/*
* Copyright 2011 Harald Welte <laforge@gnumonks.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "GSMTransfer.h"
#include "OsmoLogicalChannel.h"
#include "OsmoThreadMuxer.h"
#include <Logger.h>
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

View File

@ -0,0 +1,82 @@
/*
* Copyright 2011 Harald Welte <laforge@gnumonks.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OsmoThreadMuxer_H
#define OsmoThreadMuxer_H
#include "OsmoLogicalChannel.h"
#include <TRXManager.h>
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 */

View File

@ -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 \

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <fstream>
#include <TRXManager.h>
#include <GSML1FEC.h>
#include <GSMConfig.h>
#include <GSMSAPMux.h>
#include <GSML3RRMessages.h>
#include <OsmoLogicalChannel.h>
#include <Globals.h>
#include <Logger.h>
#include <CLI.h>
#include <CLIServer.h>
#include <CLIParser.h>
#include <PowerManager.h>
#include <RRLPQueryController.h>
#include <Configuration.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/stat.h>
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<sizeof(tempBuf)?bytesRead:sizeof(tempBuf)-1] = '\0';
int res = sscanf(tempBuf, " %d", &pid);
if (res < 1) {
LOG(ERROR) << "Unable to parse PID from file " << lockfile << ", code="
<< errno << " (" << strerror(errno) << ")";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
static int startTransceiver()
{
// Start the transceiver binary, if the path is defined.
// If the path is not defined, the transceiver must be started by some other process.
if (gConfig.defines("TRX.Path")) {
// Open and lock PID file, taking care of old transceiver instance.
sgTransceiverPidFile = gConfig.getStr("TRX.WritePID");
sgTransceiverPidFileFd = openPidFile(sgTransceiverPidFile);
if (sgTransceiverPidFileFd < 0) return EXIT_SUCCESS;
int pid;
if (lockPidFile(sgTransceiverPidFile, sgTransceiverPidFileFd, false) != EXIT_SUCCESS) {
// Another OpenBTS instance is running and blocking PID file.
return EXIT_FAILURE;
}
if (readPidFile(sgTransceiverPidFile, sgTransceiverPidFileFd, pid) == EXIT_SUCCESS) {
// There is no harm in this. Transceiver's owner is not
// running and could safely kill it.
kill(pid, SIGTERM);
}
// Start transceiver
const char *TRXPath = gConfig.getStr("TRX.Path");
const char *TRXLogLevel = gConfig.getStr("TRX.LogLevel");
const char *TRXLogFileName = NULL;
if (gConfig.defines("TRX.LogFileName")) TRXLogFileName=gConfig.getStr("TRX.LogFileName");
sgTransceiverPid = vfork();
LOG_ASSERT(sgTransceiverPid>=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 <program-name>.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<gConfig.getNum("GSM.NumC7s"); i++) {
gBTS.createCombinationVII(gTRX,sCount/8,sCount);
if (halfDuplex) sCount++;
sCount++;
}
// Create C-I slots.
for (int i=0; i<gConfig.getNum("GSM.NumC1s"); i++) {
gBTS.createCombinationI(gTRX,sCount/8,sCount);
if (halfDuplex) sCount++;
sCount++;
}
// Set up idle filling on C0 as needed.
while (sCount<8) {
gBTS.createCombination0(gTRX,sCount/8,sCount);
if (halfDuplex) sCount++;
sCount++;
}
/*
Note: The number of different paging subchannels on
the CCCH is:
MAX(1,(3 - BS-AG-BLKS-RES)) * BS-PA-MFRMS
if CCCH-CONF = "001"
(9 - BS-AG-BLKS-RES) * BS-PA-MFRMS
for other values of CCCH-CONF
*/
// Set up the pager.
// Set up paging channels.
// HACK -- For now, use a single paging channel, since paging groups are broken.
gBTS.addPCH(&CCCH2);
// Be sure we are not over-reserving.
LOG_ASSERT(gConfig.getNum("GSM.PagingReservations")<gBTS.numAGCHs());
// OK, now it is safe to start the BTS.
gBTS.start();
#endif
LOG(INFO) << "system ready";
#if 0
if (strcasecmp(gConfig.getStr("CLI.Type"),"TCP") == 0) {
ConnectionServerSocketTCP serverSock(gConfig.getNum("CLI.TCP.Port"),
gConfig.getStr("CLI.TCP.IP"));
sgCLIServerSock = &serverSock;
runCLIServer(&serverSock);
sgCLIServerSock = NULL;
} else if (strcasecmp(gConfig.getStr("CLI.Type"),"Unix") == 0) {
ConnectionServerSocketUnix serverSock(gConfig.getStr("CLI.Unix.Path"));
sgCLIServerSock = &serverSock;
runCLIServer(&serverSock);
sgCLIServerSock = NULL;
} else {
runCLI(&gParser);
}
#endif
} catch(SocketError) {
// Shutdown without core dump.
// SocketError is a usual case, e.g. it's fired when transceiver fails.
LOG(ALARM) << "Uncaught exception. Shutting down.";
}
#if 0
if (!gBTS.hold()) {
exitBTS(0, cout);
}
#endif
serverCleanup();
return EXIT_SUCCESS;
}
// vim: ts=4 sw=4