/* * Copyright 2013, 2014 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. 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. */ // Written by Pat Thompson #define LOG_GROUP LogGroup::Control // Can set Log.Level.Control for debugging #include "L3LogicalChannel.h" #include "L3MMLayer.h" #include // Needed for getL2Channel() #include // For gBTS namespace Control { using namespace GSM; void L3LogicalChannel::L3LogicalChannelReset() { LOG(DEBUG) << this; ScopedLock lock(gMMLock,__FILE__,__LINE__); // FIXMENOW Added 10-23-2013 // We could reset mNextChan too, but it is unused unless needed so dont bother. chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected)); // If we do cancel any dialogs, it is in error. LOG(DEBUG); if (mNextChan && mNextChan->mChState == chReassignTarget) { // This rare case may occur for channel loss or if the MS sends, for example, an IMSI Detach // during the channel reassignment procedure. LOG(INFO) << "lost contact with MS during reassignment procedure "<chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected)); mNextChan = NULL; } LOG(DEBUG); } void L3LogicalChannel::L3LogicalChannelInit() { // We must NOT reset mNextChan,mPrevChan as part of the LogicalChannelReset because these must // survive the establishment and release of channels during the reassignment procedure. // We dont use them for anything else, so it is ok to end with them set. mNextChan = NULL; //mPrevChan = NULL; mChState = chIdle; mChContext = NULL; L3LogicalChannelReset(); } L3LogicalChannel::~L3LogicalChannel() { chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected)); } // This virtual method should never be called; it is over-ridden by the sub-class for all channel types that matter. const char * L3LogicalChannel::descriptiveString() const { return "undefined"; } L2LogicalChannel * L3LogicalChannel::getL2Channel() { // We dont need a dynamic cast since L2 and L3 LogicalChannel are always allocated together. // But UMTS may change that. return dynamic_cast(this); } // TODO: This should probably be removed and the few uses replaced by specific functions, // like getChannelDescription and getSACCH. const L2LogicalChannel * L3LogicalChannel::getL2Channel() const { return dynamic_cast(this); } L3LogicalChannel *L3LogicalChannel::getSACCHL3() { return dynamic_cast(this->getL2Channel()->getSACCH()); } void L3LogicalChannel::l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim/*=GSM::DATA*/, SAPI_t SAPI/*=0*/) { WATCHINFO("l3sendm"<primitive()==L3_ESTABLISH_INDICATION) return req; if (req->primitive()==HANDOVER_ACCESS) return req; LOG(INFO) << "L3LogicalChannel: Ignored primitive:"<primitive(); delete req; } return NULL; // to keep the compiler happy } MMContext *L3LogicalChannel::chanGetContext(bool create) { //LOG(DEBUG); ScopedLock lock(gMMLock,__FILE__,__LINE__); //LOG(DEBUG); if (mChContext == NULL) { if (create) { mChContext = new MMContext(this); } } return mChContext; } void L3LogicalChannel::chanSetHandoverPenalty(NeighborPenalty &penalty) { chanGetContext(false)->chanSetHandoverPenalty(penalty); } // WARNING: This is called from the CLI thread. string L3LogicalChannel::chanGetImsi(bool verbose) const { ScopedLock lock(gMMLock,__FILE__,__LINE__); return mChContext ? mChContext->mmGetImsi(verbose) : string(verbose ? "no-MMChannel" : ""); } // WARNING: This is called from the CLI thread. time_t L3LogicalChannel::chanGetDuration() const { ScopedLock lock(gMMLock,__FILE__,__LINE__); return mChContext ? mChContext->mmcDuration() : 0; } //void L3LogicalChannel::chanSetContext(MMContext* wContext) //{ // chanFreeContext(); // mChContext = wContext; // mChContext->mmSetChannel(this); //} // The sipcode would be used if a SipDialog on this channel is still active, which indicates a channel loss failure // or server error. void L3LogicalChannel::chanFreeContext(TermCause cause) { LOG(DEBUG); ScopedLock lock(gMMLock,__FILE__,__LINE__); LOG(DEBUG); MMContext *save = mChContext; mChContext = NULL; if (save) { LOG(DEBUG) <setPhy(*getL2Channel()); tch->lcstart(); LOG(DEBUG) << LOGVAR2("curchan",this)<(tch); // (pat) TODO: If not VEA, we should try doing the tch->open() here to see if it reduces the number // of channel reassignment failures. return true; } // This is run on the old channel. void L3LogicalChannel::reassignStart() { ScopedLock lock(gMMLock,__FILE__,__LINE__); LOG(DEBUG) << this << LOGVAR(mNextChan); // The current channel is the SDCCH, and mNextChan is the allocated TCH the MS will use next. assert(mNextChan); // reassignmentAllocNextTCH was called first. // We set this state so when the LAPDm RELEASE arrives on this channel we dont kill everything off, // which is the normal reaction to a RELEASE. // Update: now we use the MMContext mmcChannelUseCnt. //chanSetState(L3LogicalChannel::chReassignPending); //mNextChan->chanSetContext(); Dont call this yet. It changes the channel back pointer. if (mNextChan->mChState != chIdle) { LOG(ERR) <<"At start of channel reassignment target channel is not idle:" <chanFreeContext(TermCause::Local(L3Cause::No_Transaction_Expected)); // This is supposed to be a no-op. mNextChan->mChContext = mChContext->tsDup(); // Must set directly. Does not change the channel back pointer. // We set this state on nextChan in case of channel loss - see L3LogicalChannelReset mNextChan->chanSetState(L3LogicalChannel::chReassignTarget); GSM::L2LogicalChannel *tch = mNextChan->getL2Channel(); GSM::L2LogicalChannel *sdcch = this->getL2Channel(); // Note we do not want to do a HARDRELEASE if this fails, because that bypasses the very timer we are supposed to be using. LOG(INFO) << "sending AssignmentCommand for " << tch << " on " << this; tch->lcopen(); // This sets T3101 as a side effect. tch->setPhy(*sdcch); } // This occurs on the channel being assigned from. // We need to release the nextChannel. Caller takes care of this chan. // Release of nextChan also occurs if a channel drops out of the main service loop and the nextChan state is still ReassignTarget void L3LogicalChannel::reassignFailure() { ScopedLock lock(gMMLock,__FILE__,__LINE__); LOG(DEBUG) << this; // Clean up the next chan we were trying to reassign to. if (mNextChan) { devassert(mNextChan->isTCHF()); mNextChan->chanSetState(L3LogicalChannel::chRequestRelease); // This will probably block the chan for 30 seconds. // If we never received the ESTABLISH primitive on nextChan, then the service loop is not runnning, // so it will never detect the change of state on nextChan, so we must free the channel here. // The service loops check dcch->chanRunning(), so they will not do anything that might try to use the Context we are freeing.. mNextChan->chanFreeContext(TermCause::Local(L3Cause::Channel_Assignment_Failure)); mNextChan = NULL; } else { LOG(ERR) << "reassignment failure but no nextChan? "<l3sendm(L3ReleaseComplete(l3ti,l3cause)); // Release the transaction identifier. // We might as well drop the whole channel. // Old code had cause 4, but that does not seem right: // RR Cause 0x04 -- "abnormal release, no activity on the radio path" // Caller does this now. //l3sendm(GSM::L3ChannelRelease(L3RRCause::NoActivityOnTheRadio)); // The MS is supposed to release the channel, which will send a RELEASE primitive up to Layer3 to close the channel. } // Both old and new L3LogicalChannel point to the same MMContext. // The message arrives on the new channel, but we run this function on the old channel // because we have not changed the LogicalChannel that the Context points to yet. // mNextChan still points to the new channel. // Beware that the two channels are serviced by different threads. void L3LogicalChannel::reassignComplete() { { ScopedLock lock(gMMLock,__FILE__,__LINE__); //timerStop(TChReassignment); // Handled by assignTCHFProcedure, which is notified after us. if (!mChContext) { // Logic error. LOG(ERR) << "received channel reassignment complete on dead channel:"<mChState != chEstablished) { // The nextChan was supposed to get an ESTABLISH primitive then the L3AssignComplete command in order to get here. // There could be a logic error or the MS may have dropped the channel at this inopportune moment, // so it is not necessarily an error. LOG(NOTICE)<< "Next channel in unexpected state, dropping channel"<mmSetChannel(mNextChan); LOG(INFO) <<"successful channel reassignment" <chanSetState(L3LogicalChannel::chReassignFailure); //mNextChan->mPrevChan = NULL; mNextChan = NULL; } chanFreeContext(); } #endif // Set the flag, which will perform the channel release from the channel serviceloop. void L3LogicalChannel::chanRelease(Primitive prim,TermCause cause) { OBJLOG(DEBUG) << prim; //chanFreeContext(cause); switch (prim) { case L3_HARDRELEASE_REQUEST: chanSetState(L3LogicalChannel::chRequestHardRelease); return; case L3_RELEASE_REQUEST: chanSetState(L3LogicalChannel::chRequestRelease); return; default: assert(0); } } // This completely releases the channel and all transactions on it. // FIXME no it doesnt, and L2 can hang when we send the primitive, so these transactions and dialogs // are not cleaned up until the next time the channel is used. Very bad. // pat 7-2014 Update: Above bug probably fixed by GSMLogicalChannel rewrite. void L3LogicalChannel::chanClose(RRCause rrcause,Primitive prim,TermCause upstreamCause) { // Note: timer expiry may indicate unresponsive MS so this may block for 30 seconds. l3sendm(L3ChannelRelease(rrcause)); chanRelease(prim,upstreamCause); } //void L3LogicalChannel::chanSetVoiceTran(TranEntry *tran) //{ // MMContext *set = chanGetContext(true); // set->tsSetVoiceTran(tran); //} RefCntPointer L3LogicalChannel::chanGetVoiceTran() { MMContext *set = chanGetContext(true); return set->tsGetVoiceTran(); } //void L3LogicalChannel::chanEnqueueFrame(L3Frame *frame) //{ // ml3UplinkQ.write(frame); //} // When L3 wants to drop a channel, it must set a flag in the L3LogicalChannel, which will be queried here. // Return false to drop the channel. bool L3LogicalChannel::chanRunning() { // Check for channel release. switch (this->mChState) { case L3LogicalChannel::chEstablished: case L3LogicalChannel::chReassignTarget: //case L3LogicalChannel::chReassignPending: return true; // Still running. case L3LogicalChannel::chIdle: // seeing this would be a bug. case L3LogicalChannel::chRequestRelease: case L3LogicalChannel::chRequestHardRelease: //case L3LogicalChannel::chReassignFailure: //case L3LogicalChannel::chReassignComplete: //case L3LogicalChannel::chReleased: return false; } return false; } const char *L3LogicalChannel::ChannelState2Text(ChannelState chstate) { switch (chstate) { case L3LogicalChannel::chIdle: return "Idle"; case L3LogicalChannel::chEstablished: return "Established"; //case L3LogicalChannel::chReleased: return "Released"; case L3LogicalChannel::chRequestRelease: return "RequestRelease"; case L3LogicalChannel::chRequestHardRelease: return "RequestHardRelease"; case L3LogicalChannel::chReassignTarget: return "ReassignTarget"; //case L3LogicalChannel::chReassignPending: return "ReassignPending"; //case L3LogicalChannel::chReassignComplete: return "ReassignmentComplete"; //case L3LogicalChannel::chReassignFailure: return "ReassignmentFailure"; } return "(chstate undefined)"; } // Info about just this L3LogicalChannel std::ostream& L3LogicalChannel::chanText(ostream& os) const { os << descriptiveString() << LOGVAR2("state",ChannelState2Text(mChState)); if (mNextChan) { os <descriptiveString()); } return os; } // Info about just the underlying MMContext for this L3LogicalChannel. // Warning: This is called from the CLI thread. std::ostream& L3LogicalChannel::chanContextText(ostream& os) const { ScopedLock lock(gMMLock,__FILE__,__LINE__); if (MMContext *mmc = Unconst(this)->chanGetContext(false)) { mmc->mmcText(os); } return os; } ostream& operator<<(ostream& os, const L3LogicalChannel& chan) { chan.chanText(os); return os; } std::ostream& operator<<(std::ostream&os, const L3LogicalChannel*ch) { if (ch) { ch->chanText(os); } else { os << "(null channel)"; } return os; } // pat FIXME - Called from CLI so must lock channel. // Warning: This is called from the CLI thread. void L3LogicalChannel::getTranIds(TranEntryList &tids) const { ScopedLock lock(gMMLock,__FILE__,__LINE__); tids.clear(); if (const MMContext *set = Unconst(this)->chanGetContext(false)) { set->getTranIds(tids); } } bool L3LogicalChannel::isTCHF() const { return chtype()==GSM::FACCHType; } bool L3LogicalChannel::isSDCCH() const { return chtype()==GSM::SDCCHType; } // For use by the CLI. void printChansInfo(std::ostream&os) { L2ChanList chans; gBTS.getChanVector(chans); for (L2ChanList::iterator it = chans.begin(); it != chans.end(); it++) { L3LogicalChannel *chan = dynamic_cast(*it); os << chan; chan->chanContextText(os); os << endl; } } }; // namespace