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...master
parent
1c41c4e0ae
commit
dd9ba70f53
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 */
|
||||
//@{
|
||||
|
|
|
@ -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 = \
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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 */
|
|
@ -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 \
|
||||
|
|
|
@ -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) {
|
||||