TCHFACCHLogicalChannel *GSMConfig::getTCH() { mLock.lock(); TCHFACCHLogicalChannel *chan = getChan<TCHFACCHLogicalChannel>(mTCHPool); if (chan) chan->open(); mLock.unlock(); return chan; }
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); }
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); }
// 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; }
// 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)"); }
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); }
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); } }
/** 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); } }
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); }