예제 #1
0
TCHFACCHLogicalChannel *GSMConfig::getTCH()
{
	mLock.lock();
	TCHFACCHLogicalChannel *chan = getChan<TCHFACCHLogicalChannel>(mTCHPool);
	if (chan) chan->open();
	mLock.unlock();
	return chan;
}
예제 #2
0
void GSMConfig::createCombinationI(TransceiverManager& TRX, unsigned CN, unsigned TN)
{
	LOG_ASSERT((CN!=0)||(TN!=0));
	LOG(NOTICE) << "Configuring combination I on C" << CN << "T" << TN;
	ARFCNManager *radio = TRX.ARFCN(CN);
	radio->setSlot(TN,1);	// (pat) 1 => Transciever.h enum ChannelCombination = I
	TCHFACCHLogicalChannel* chan = new TCHFACCHLogicalChannel(CN,TN,gTCHF_T[TN]);
	chan->downstream(radio);
	Thread* thread = new Thread;
	thread->start((void*(*)(void*))Control::DCCHDispatcher,chan);
	chan->open();
	gBTS.addTCH(chan);
}
예제 #3
0
void GSMConfig::createCombinationI(TransceiverManager& TRX, unsigned CN, unsigned TN)
{
    LOG_ASSERT((CN!=0)||(TN!=0));
    LOG(NOTICE) << "Configuring combination I on C" << CN << "T" << TN;
    ARFCNManager *radio = TRX.ARFCN(CN);
    radio->setSlot(TN,1);	// (pat) 1 => Transciever.h enum ChannelCombination = I
    TCHFACCHLogicalChannel* chan = new TCHFACCHLogicalChannel(CN,TN,gTCHF_T[TN]);
    chan->downstream(radio);
    Thread* thread = new Thread;
    thread->start((void*(*)(void*))Control::DCCHDispatcher,dynamic_cast<L3LogicalChannel*>(chan));
    chan->lcinit();
    if (CN == 0 && !testStart) chan->lcstart();	// Everything on C0 must broadcast continually.
    gBTS.addTCH(chan);
}
예제 #4
0
// 6-2014 pat: The channel is now returned with T3101 running but un-started, which means it is not yet transmitting.
// The caller is responsible for setting the Timing Advance and then starting it.
TCHFACCHLogicalChannel *GSMConfig::getTCH(
    bool forGPRS,	// If true, allocate the channel to gprs, else to RR use.
    bool onlyCN0)	// If true, allocate only channels on the lowest ARFCN.
{
    LOG(DEBUG);
    ScopedLock lock(mLock);
    //if (GPRS::GPRSDebug) {
    //	const unsigned sz = mTCHPool.size();
    //	char buf[300];  int n = 0;
    //	for (unsigned i=0; i<sz; i++) {
    //		TCHFACCHLogicalChannel *chan = mTCHPool[i];
    //		n += sprintf(&buf[n],"ch=%d:%d,g=%d,r=%d ",chan->CN(),chan->TN(),
    //				chan->inUseByGPRS(),chan->recyclable());
    //	}
    //	LOG(WARNING)<<"getTCH list:"<<buf;
    //}
    TCHFACCHLogicalChannel *chan = getChan<TCHFACCHLogicalChannel>(mTCHPool,forGPRS);
    // (pat) We have to open it or set gprs mode before returning to avoid a race.
    if (chan) {
        // The channels are searched in order from low to high, so if the first channel
        // found is not on CN0, we have failed.
        //LOG(DEBUG)<<"getTCH returns"<<LOGVAR2("chan->CN",chan->CN());
        if (onlyCN0 && chan->CN()) {
            return NULL;
        }
        if (forGPRS) {
            // (pat) Reserves channel for GPRS, but does not start delivering bursts yet.
            chan->lcGetL1()->setGPRS(true,NULL);
            return chan;
        }
        gReports.incr("OpenBTS.GSM.RR.ChannelAssignment");
        chan->lcinit();
    } else {
        //LOG(DEBUG)<<"getTCH returns NULL";
    }
    LOG(DEBUG);
    return chan;
}
예제 #5
0
// Dispatching loop, runs for the lifetime of the connection
static void connDispatchLoop(LogicalChannel* chan, unsigned int id)
{
	TCHFACCHLogicalChannel* tch = dynamic_cast<TCHFACCHLogicalChannel*>(chan);
	LOG(INFO) << "starting dispatch loop for connection " << id << (tch ? " with traffic" : "");
	if (chan->SACCH())
		chan->SACCH()->measurementHoldOff();
	Timeval tPhy(PHY_INTERVAL);
	unsigned int maxQ = gConfig.getNum("GSM.MaxSpeechLatency");
	unsigned int tOut = tch ? 5 : 20; // TODO
	while (gSigConn.valid() && (gConnMap.find(id) == chan)) {
		if (tch) {
			while (tch->queueSize() > maxQ)
				delete[] tch->recvTCH();
			unsigned char* mFrame = tch->recvTCH();
			if (mFrame) {
				gMediaConn.send(id,mFrame,33);
				delete mFrame;
			}
		}
		unsigned char sapi;
		L3Frame* frame = 0;
		for (sapi = 1; sapi < 4; sapi++) {
			if (chan->debugGetL2(sapi))
				frame = chan->recv(0,sapi);
			if (frame)
				break;
		}
		if (!frame) {
			sapi = 0;
			frame = chan->recv(tOut,0);
		}
		if (!frame)
			continue;
		switch (frame->primitive()) {
			case ERROR:
				LOG(NOTICE) << "error reading on connection " << id;
				break;
			case DATA:
			case UNIT_DATA:
				if (tPhy.passed()) {
					tPhy.future(PHY_INTERVAL);
					sendPhyInfo(chan,id);
				}
				if (!connDispatchRR(chan,id,frame))
					gSigConn.send(sapi,id,frame);
			case HANDOVER_ACCESS:
				delete frame;
				continue;
			case RELEASE:
			case HARDRELEASE:
				break;
			case ESTABLISH:
				if (sapi) {
					delete frame;
					gSigConn.send(SigEstablishSAPI,sapi,id);
					continue;
				}
				chan->send(GSM::RELEASE);
				// fall through
			default:
				LOG(ERR) << "unexpected primitive " << frame->primitive();
				break;
		}
		// Frame was not handled - delete it and end connection
		delete frame;
		break;
	}
	const LogicalChannel* ch = gConnMap.find(id);
	if (ch == chan)
		gSigConn.send(Connection::SigConnLost,0,id);
	else if (!ch)
		chan->send(GSM::RELEASE);
	LOG(INFO) << "ending dispatch loop for connection " << id <<
		(ch ? ((ch == chan) ? " (remote release)" : " (reassigned)") : " (local close)");
}
예제 #6
0
void Control::EmergencyCall(const L3CMServiceRequest* req, LogicalChannel *LCH)
{
	assert(req);
	LOG(ALARM) << "starting emergency call from request " << *req;
	assert(LCH);
	TCHFACCHLogicalChannel* TCH = dynamic_cast<TCHFACCHLogicalChannel*>(LCH);
	assert(TCH);

	// If we got a TMSI, find the IMSI.
	L3MobileIdentity mobileIdentity = req->mobileIdentity();
	if (mobileIdentity.type()==TMSIType) {
		const char *IMSI = gTMSITable.IMSI(mobileIdentity.TMSI());
		if (IMSI) mobileIdentity = L3MobileIdentity(IMSI);
	}

	// Can't find the TMSI?  Ask for an IMSI.
	if (mobileIdentity.type()==TMSIType) {
		LOG(NOTICE) << "E-MOC with no IMSI or IMEI.  Reqesting IMSI.";
		TCH->send(L3IdentityRequest(IMSIType));
		// FIXME -- This request times out on T3260, 12 sec.  See GSM 04.08 Table 11.2.
		L3Message* msg_resp = getMessage(TCH);
		L3IdentityResponse *resp = dynamic_cast<L3IdentityResponse*>(msg_resp);
		if (!resp) {
			if (msg_resp) {
				LOG(WARN) << "Unexpected message " << *msg_resp;
				delete msg_resp;
			}
			throw UnexpectedMessage();
		}
		mobileIdentity = resp->mobileID();
		delete msg_resp;
	}

	// Still no valid ID??  Get the IMEI.
	if (mobileIdentity.type()==TMSIType) {
		LOG(NOTICE) << "E-MOC with no IMSI or IMEI.  Reqesting IMEI.";
		TCH->send(L3IdentityRequest(IMSIType));
		// FIXME -- This request times out on T3260, 12 sec.  See GSM 04.08 Table 11.2.
		L3Message* msg_resp = getMessage(TCH);
		L3IdentityResponse *resp = dynamic_cast<L3IdentityResponse*>(msg_resp);
		if (!resp) {
			if (msg_resp) {
				LOG(WARN) << "Unexpected message " << *msg_resp;
				delete msg_resp;
			}
			throw UnexpectedMessage();
		}
		mobileIdentity = resp->mobileID();
		delete msg_resp;
	}

	// Still no valid ID???  F*, just make something up!
	if (mobileIdentity.type()==TMSIType) {
		LOG(WARN) << "E-MOC with no identity, forcing to null IMSI.";
		mobileIdentity = L3MobileIdentity("000000000000000");
	}

	// Let the phone know we're going ahead with the transaction.
	LOG(NOTICE) << "sending CMServiceAccept";
	TCH->send(L3CMServiceAccept());

	// Get the Setup message.
	L3Message* msg_setup = getMessage(TCH);
	const L3EmergencySetup *setup = dynamic_cast<const L3EmergencySetup*>(msg_setup);
	if (!setup) {
		if (msg_setup) {
			LOG(WARN) << "Unexpected message " << *msg_setup;
			delete msg_setup;
		}
		throw UnexpectedMessage();
	}
	LOG(NOTICE) << *setup;
	// Pull out the L3 short transaction information now.
	// See GSM 04.07 11.2.3.1.3.
	unsigned L3TI = setup->TIValue();

	// Make a copy.  Don't forget to delete it later.
	char *bcd_digits = strdup(gConfig.getStr("PBX.Emergency"));

	LOG(DEBUG) << "SIP start engine";
	// Create a transaction table entry so the TCH controller knows what to do later.
	// The transaction on the TCH is a continuation of this one and uses the same ID
	TransactionEntry transaction(mobileIdentity,
		req->serviceType(),
		L3TI, L3CalledPartyBCDNumber(bcd_digits));
	assert(transaction.TIFlag()==0);
	if (mobileIdentity.type()!=TMSIType) transaction.SIP().User(mobileIdentity.digits());
	transaction.Q931State(TransactionEntry::MOCInitiated);
	TCH->transactionID(transaction.ID());
	LOG(DEBUG) << "transaction: " << transaction;
	gTransactionTable.add(transaction);

	// Done with the setup message.
	delete msg_setup;

	// Now start a call by contacting asterisk.
	// Engine methods will return their current state.	
	// The remote party will start ringing soon.
	LOG(NOTICE) << "starting SIP (INVITE) Calling "<<bcd_digits;
	unsigned basePort = allocateRTPPorts();
	SIPState state = transaction.SIP().MOCSendINVITE(bcd_digits,gConfig.getStr("SIP.IP"),basePort,SIP::RTPGSM610);
	LOG(DEBUG) << "SIP state="<<state;
	LOG(DEBUG) << "Q.931 state=" << transaction.Q931State();

	free(bcd_digits);

	// For very early assignment, we need a mode change.
	static const L3ChannelMode mode(L3ChannelMode::SpeechV1);
	TCH->send(L3ChannelModeModify(TCH->channelDescription(),mode));
	L3Message *msg_ack = getMessage(TCH);
	const L3ChannelModeModifyAcknowledge *ack =
		dynamic_cast<L3ChannelModeModifyAcknowledge*>(msg_ack);
	if (!ack) {
		if (msg_ack) {
			LOG(WARN) << "Unexpected message " << *msg_ack;
			delete msg_ack;
		}
		throw UnexpectedMessage(transaction.ID());
	}
	// Cause 0x06 is "channel unacceptable"
	bool modeOK = (ack->mode()==mode);
	delete msg_ack;
	if (!modeOK) return abortCall(transaction,TCH,L3Cause(0x06));

	// From here on, it's normal call setup.
	MOCController(transaction,TCH);
}
예제 #7
0
void Control::MTCStarter(TransactionEntry& transaction, LogicalChannel *LCH)
{
	assert(LCH);
	LOG(INFO) << "MTC on " << LCH->type() << " transaction: "<< transaction;

	// Determine if very early assigment already happened.
	bool veryEarly = false;
	if (LCH->type()==FACCHType) veryEarly=true;

	// Allocate a TCH for the call.
	TCHFACCHLogicalChannel *TCH = NULL;
	if (!veryEarly) {
		TCH = allocateTCH(dynamic_cast<SDCCHLogicalChannel*>(LCH));
		// It's OK to just return on failure; allocateTCH cleaned up already.
		// The orphaned transaction will be cleared automatically later.
		if (TCH==NULL) return;
	}


	// Get transaction identifiers.
	// This transaction was created by the SIPInterface when it
	// processed the INVITE that started this call.
	if (!veryEarly) TCH->transactionID(transaction.ID());	
	LCH->transactionID(transaction.ID());	
	unsigned L3TI = transaction.TIValue();
	assert(transaction.TIFlag()==1);

	// GSM 04.08 5.2.2.1
	LOG(INFO) << "sending GSM Setup to call " << transaction.calling();
	LCH->send(L3Setup(0,L3TI,L3CallingPartyBCDNumber(transaction.calling())));
	transaction.T303().set();
	transaction.Q931State(TransactionEntry::CallPresent);
	gTransactionTable.update(transaction);

	// Wait for Call Confirmed message.
	LOG(DEBUG) << "wait for GSM Call Confirmed";
	while (transaction.Q931State()!=TransactionEntry::MTCConfirmed) {
		if (transaction.SIP().MTCSendTrying()==SIP::Fail) {
			LOG(NOTICE) << "call failed on SIP side";
			LCH->send(RELEASE);
			// Cause 0x03 is "no route to destination"
			return abortCall(transaction,LCH,L3Cause(0x03));
		}
		// FIXME -- What's the proper timeout here?
		// It's the SIP TRYING timeout, whatever that is.
		if (updateGSMSignalling(transaction,LCH,1000)) {
			LOG(INFO) << "Release from GSM side";
			LCH->send(RELEASE);
			return;
		}
		// Check for SIP cancel, too.
		if (transaction.SIP().MTCWaitForACK()==SIP::Fail) {
			LOG(NOTICE) << "call failed on SIP side";
			LCH->send(RELEASE);
			// Cause 0x10 is "normal clearing"
			return abortCall(transaction,LCH,L3Cause(0x10));
		}
	}

	// The transaction is moving to the MTCController.
	// Once this update happens, don't change the transaction object again in this function.
	gTransactionTable.update(transaction);
	LOG(DEBUG) << "transaction: " << transaction;
	if (veryEarly) {
		// For very early assignment, we need a mode change.
		static const L3ChannelMode mode(L3ChannelMode::SpeechV1);
		LCH->send(L3ChannelModeModify(LCH->channelDescription(),mode));
		L3Message* msg_ack = getMessage(LCH);
		const L3ChannelModeModifyAcknowledge *ack =
			dynamic_cast<L3ChannelModeModifyAcknowledge*>(msg_ack);
		if (!ack) {
			if (msg_ack) {
				LOG(WARN) << "Unexpected message " << *msg_ack;
				delete msg_ack;
			}
			throw UnexpectedMessage(transaction.ID());
		}
		// Cause 0x06 is "channel unacceptable"
		bool modeOK = (ack->mode()==mode);
		delete msg_ack;
		if (!modeOK) return abortCall(transaction,LCH,L3Cause(0x06));
		MTCController(transaction,dynamic_cast<TCHFACCHLogicalChannel*>(LCH));
	}
	else {
		// For late assignment, send the TCH assignment now.
		// This dispatcher on the next channel will continue the transaction.
		assignTCHF(transaction,dynamic_cast<SDCCHLogicalChannel*>(LCH),TCH);
	}
}
예제 #8
0
/**
	This function starts MOC on the SDCCH to the point of TCH assignment. 
	@param req The CM Service Request that started all of this.
	@param LCH The logical used to initiate call setup.
*/
void Control::MOCStarter(const L3CMServiceRequest* req, LogicalChannel *LCH)
{
	assert(LCH);
	assert(req);
	LOG(INFO) << *req;

	// Determine if very early assignment already happened.
	bool veryEarly = (LCH->type()==FACCHType);

	// If we got a TMSI, find the IMSI.
	// Note that this is a copy, not a reference.
	L3MobileIdentity mobileIdentity = req->mobileIdentity();
	resolveIMSI(mobileIdentity,LCH);


	// FIXME -- At this point, verify the that subscriber has access to this service.
	// If the subscriber isn't authorized, send a CM Service Reject with
	// cause code, 0x41, "requested service option not subscribed",
	// followed by a Channel Release with cause code 0x6f, "unspecified".
	// Otherwise, proceed to the next section of code.
	// For now, we are assuming that the phone won't make a call if it didn't
	// get registered.

	// Allocate a TCH for the call, if we don't have it already.
	TCHFACCHLogicalChannel *TCH = NULL;
	if (!veryEarly) {
		TCH = allocateTCH(dynamic_cast<SDCCHLogicalChannel*>(LCH));
		// It's OK to just return on failure; allocateTCH cleaned up already,
		// and the SIP side and transaction record don't exist yet.
		if (TCH==NULL) return;
	}

	// Let the phone know we're going ahead with the transaction.
	LOG(DEBUG) << "sending CMServiceAccept";
	LCH->send(L3CMServiceAccept());

	// Get the Setup message.
	// GSM 04.08 5.2.1.2
	const L3Setup *setup = NULL;
	L3Message* msg_setup = NULL;
	while (msg_setup = getMessage(LCH)) {
		setup = dynamic_cast<const L3Setup*>(msg_setup);
		if (!setup) {
			L3GPRSSuspensionRequest *r = dynamic_cast<L3GPRSSuspensionRequest*>(msg_setup);
			if (!r) {
				if (msg_setup) {
					LOG(WARN) << "Unexpected message " << *msg_setup;
					delete msg_setup;
				}
				throw UnexpectedMessage();
			} else {
				LOG(INFO) << "Ignored L3 RR GPRS Suspension Request.";
				if (msg_setup) delete msg_setup;
				continue;
			}
		}
		break;
	}
	LOG(INFO) << *setup;
	// Pull out the L3 short transaction information now.
	// See GSM 04.07 11.2.3.1.3.
	unsigned L3TI = setup->TIValue();
	if (!setup->haveCalledPartyBCDNumber()) {
		// FIXME -- This is quick-and-dirty, not following GSM 04.08 5.
		LOG(WARN) << "MOC setup with no number";
		// Cause 0x60 "Invalid mandatory information"
		LCH->send(L3ReleaseComplete(1,L3TI,L3Cause(0x60)));
		LCH->send(L3ChannelRelease());
		// The SIP side and transaction record don't exist yet.
		// So we're done.
		delete msg_setup;
		return;
	}

	LOG(DEBUG) << "SIP start engine";
	// Get the users sip_uri by pulling out the IMSI.
	const char *IMSI = mobileIdentity.digits();
	// Pull out Number user is trying to call and use as the sip_uri.
	const char *bcd_digits = setup->calledPartyBCDNumber().digits();

	// Create a transaction table entry so the TCH controller knows what to do later.
	// The transaction on the TCH is a continuation of this one and uses the same ID.
	TransactionEntry transaction(mobileIdentity,
		req->serviceType(),
		L3TI,
		setup->calledPartyBCDNumber());
	assert(transaction.TIFlag()==0);
	transaction.SIP().User(IMSI);
	transaction.Q931State(TransactionEntry::MOCInitiated);
	LCH->transactionID(transaction.ID());
	if (!veryEarly) TCH->transactionID(transaction.ID());
	LOG(DEBUG) << "transaction: " << transaction;
	gTransactionTable.add(transaction);

	// At this point, we have enough information start the SIP call setup.
	// We also have a SIP side and a transaction that will need to be
	// cleaned up on abort or clearing.

	// Now start a call by contacting asterisk.
	// Engine methods will return their current state.	
	// The remote party will start ringing soon.
	LOG(DEBUG) << "starting SIP (INVITE) Calling "<<bcd_digits;
	unsigned basePort = allocateRTPPorts();
	SIPState state = transaction.SIP().MOCSendINVITE(bcd_digits,gConfig.getStr("SIP.IP"),basePort,SIP::RTPGSM610);
	LOG(DEBUG) << "SIP state="<<state;
	LOG(DEBUG) << "Q.931 state=" << transaction.Q931State();

	// Once we can start SIP call setup, send Call Proceeding.
	LOG(DEBUG) << "Sending Call Proceeding";
	LCH->send(L3CallProceeding(1,L3TI));
	transaction.Q931State(TransactionEntry::MOCProceeding);
	gTransactionTable.update(transaction);
	// Finally done with the Setup message.
	delete msg_setup;

	// The transaction is moving on to the MOCController.
	// If we need a TCH assignment, we do it here.
	gTransactionTable.update(transaction);
	LOG(DEBUG) << "transaction: " << transaction;
	if (veryEarly) {
		// For very early assignment, we need a mode change.
		static const L3ChannelMode mode(L3ChannelMode::SpeechV1);
		LCH->send(L3ChannelModeModify(LCH->channelDescription(),mode));
		L3Message *msg_ack = getMessage(LCH);
		const L3ChannelModeModifyAcknowledge *ack =
			dynamic_cast<L3ChannelModeModifyAcknowledge*>(msg_ack);
		if (!ack) {
			if (msg_ack) {
				LOG(WARN) << "Unexpected message " << *msg_ack;
				delete msg_setup;
			}
			throw UnexpectedMessage(transaction.ID());
		}
		// Cause 0x06 is "channel unacceptable"
		bool modeOK = (ack->mode()==mode);
		delete msg_ack;
		if (!modeOK) return abortCall(transaction,LCH,L3Cause(0x06));
		MOCController(transaction,dynamic_cast<TCHFACCHLogicalChannel*>(LCH));
	} else {
		// For late assignment, send the TCH assignment now.
		// This dispatcher on the next channel will continue the transaction.
		assignTCHF(transaction,dynamic_cast<SDCCHLogicalChannel*>(LCH),TCH);
	}
}
예제 #9
0
int main(int argc, char *argv[])
{
	srandom(time(NULL));

	COUT("\n\n" << gOpenBTSWelcome << "\n");
	COUT("\nStarting the system...");

	gSetLogLevel(gConfig.getStr("LogLevel"));
	if (gConfig.defines("LogFileName")) {
		gSetLogFile(gConfig.getStr("LogFileName"));
	}

	// Start the transceiver binary, if the path is defined.
	// If the path is not defined, the transceiver must be started by some other process.
	const char *TRXPath = NULL;
	if (gConfig.defines("TRX.Path")) TRXPath=gConfig.getStr("TRX.Path");
	pid_t transceiverPid = 0;
	if (TRXPath) {
		const char *TRXLogLevel = gConfig.getStr("TRX.LogLevel");
		const char *TRXLogFileName = NULL;
		if (gConfig.defines("TRX.LogFileName")) TRXLogFileName=gConfig.getStr("TRX.LogFileName");
		transceiverPid = vfork();
		assert(transceiverPid>=0);
		if (transceiverPid==0) {
			execl(TRXPath,"transceiver",TRXLogLevel,TRXLogFileName,NULL);
			LOG(ERROR) << "cannot start transceiver";
			_exit(0);
		}
	}

	// Start the SIP interface.
	gSIPInterface.start();

	// Start the transceiver interface.
	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 BSC everywhere.
	radio->setTSC(gBTS.BCC());
	// Tune.
      	radio->tune(gConfig.getNum("GSM.ARFCN"));
	// C-V on C0T0
	radio->setSlot(0,5);

	// Turn on and power up.
	radio->powerOn();
       	radio->setPower(gConfig.getNum("GSM.PowerAttenDB"));

	// set up a combination V beacon set

	// 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 SDCCH[4] = { 
		SDCCHLogicalChannel(0,gSDCCH_4_0),
		SDCCHLogicalChannel(0,gSDCCH_4_1),
		SDCCHLogicalChannel(0,gSDCCH_4_2),
		SDCCHLogicalChannel(0,gSDCCH_4_3)
	};
	Thread SDCCHControlThread[4];
	for (int i=0; i<4; i++) {
		SDCCH[i].downstream(radio);
		SDCCHControlThread[i].start((void*(*)(void*))Control::DCCHDispatcher,&SDCCH[i]);
		SDCCH[i].open();
		gBTS.addSDCCH(&SDCCH[i]);
	}


	// Count configured slots.
	unsigned sCount = 1;

	// Create C-VII slots on C0Tn
	for (unsigned i=0; i<gConfig.getNum("GSM.NumC7s"); i++) {
		radio->setSlot(sCount,7);
		for (unsigned sub=0; sub<8; sub++) {
			SDCCHLogicalChannel* chan = new SDCCHLogicalChannel(sCount,gSDCCH8[sub]);
			chan->downstream(radio);
			Thread* thread = new Thread;
			thread->start((void*(*)(void*))Control::DCCHDispatcher,chan);
			chan->open();
			gBTS.addSDCCH(chan);
		}
		sCount++;
	}


	// Create C-I slots on C0Tn
	for (unsigned i=0; i<gConfig.getNum("GSM.NumC1s"); i++) {
		radio->setSlot(sCount,1);
		TCHFACCHLogicalChannel* chan = new TCHFACCHLogicalChannel(sCount,gTCHF_T[sCount]);
		chan->downstream(radio);
		Thread* thread = new Thread;
		thread->start((void*(*)(void*))Control::DCCHDispatcher,chan);
		chan->open();
		gBTS.addTCH(chan);
		sCount++;
	}

	assert(sCount<=8);



	/*
		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.
	gBTS.addPCH(&CCCH2);
	// Start the paging generator
	// Don't start the pager until some PCHs exist!!
	gBTS.pager().start();

	LOG(INFO) << "system ready";
	COUT("\n\nWelcome to OpenBTS.  Type \"help\" to see available commands.");
        // FIXME: We want to catch control-d (emacs keybinding for exit())

	while (1) {
		char inbuf[1024];
		cout << "\nOpenBTS> ";
		cin.getline(inbuf,1024,'\n');
		if (strcmp(inbuf,"exit")==0) break;
		gParser.process(inbuf,cout,cin);
	}

	if (transceiverPid) kill(transceiverPid,SIGKILL);
}