Files
openbts/Control/L3LogicalChannel.cpp

472 lines
17 KiB
C++

/*
* 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 <GSMLogicalChannel.h> // Needed for getL2Channel()
#include <GSMConfig.h> // 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 "<<this; // Or some other error.
// TODO:
// This state indicates the other channel is idle, but it is dangerous to free
// it from here because it could receive a primitive and become active at any time.
// If that happens it will get a new MMContext and try to fire up, not sure what happens then.
mNextChan->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<L2LogicalChannel*>(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<const L2LogicalChannel*>(this);
}
L3LogicalChannel *L3LogicalChannel::getSACCHL3() {
return dynamic_cast<L3LogicalChannel*>(this->getL2Channel()->getSACCH());
}
void L3LogicalChannel::l3sendm(const GSM::L3Message& msg, const GSM::Primitive& prim/*=GSM::DATA*/, SAPI_t SAPI/*=0*/)
{
WATCHINFO("l3sendm"<<LOGVAR(prim)<<LOGVAR(SAPI)<<LOGVARP(msg)<<" "<<this); // 'this' is the descriptive string of the channel.
l2sendm(msg,prim,SAPI);
}
// These days this is used only for the handover command, which was sent as a pre-formed L3-message from BTS2 to BTS1.
// Note that SAP is encoded in the L3Frame.
void L3LogicalChannel::l3sendf(const GSM::L3Frame& frame)
{
// 3-14-2014 pat: Changed the LOG levels, formerly we sent the frame to INFO and the message to DEBUG, but the frame is raw, so I reversed it.
LOG(DEBUG) <<this <<LOGVARP(frame);
if (IS_LOG_LEVEL(INFO)) {
if (const L3Message *msg = parseL3(frame)) {
WATCHINFO(this <<" sendf "<<*msg);
delete msg;
}
}
l2sendf(frame);
}
// WARNING: If you send a RELEASE and the channel is not responding, this blocks for 30 seconds.
// We only call this from the thread service loop, so it is the last thing we ever do in the thread.
void L3LogicalChannel::l3sendp(const GSM::Primitive& prim, SAPI_t SAPI)
{
WATCHINFO("l3sendp"<<LOGVAR(prim)<<LOGVAR(SAPI)<<" "<<this); // 'this' is the descriptive string of the channel.
//if (prim == RELEASE || prim == HARDRELEASE) {
//chanSetState(chReleased); // Inform the service loop it should exit.
//}
l2sendp(prim,SAPI);
};
L3Frame* L3LogicalChannel::waitForEstablishOrHandover()
{
while (true) {
L3Frame *req = l2recv();
LOG(DEBUG) <<LOGVAR(req);
if (req==NULL) continue;
if (req->primitive()==L3_ESTABLISH_INDICATION) return req;
if (req->primitive()==HANDOVER_ACCESS) return req;
LOG(INFO) << "L3LogicalChannel: Ignored primitive:"<<req->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) <<this;
gMMLayer.mmFreeContext(save,cause);
}
LOG(DEBUG);
}
// See 44.018 3.1.4: "Change of Dedicated Channels"
bool L3LogicalChannel::reassignAllocNextTCH() // For a channel reassignment procedure.
{
ScopedLock lock(gMMLock,__FILE__,__LINE__);
GSM::TCHFACCHLogicalChannel *tch = gBTS.getTCH();
if (tch==NULL) {
LOG(DEBUG) << LOGVAR2("curchan",this)<<LOGVAR2("nextchan","null,congestion");
return false;
}
// Copy the phy params from old to new channel, then fire it up.
tch->setPhy(*getL2Channel());
tch->lcstart();
LOG(DEBUG) << LOGVAR2("curchan",this)<<LOGVAR2("nextchan",tch);
// When we receive confirmation from the MS, mNextChannel will become mChannel.
mNextChan = dynamic_cast<L3LogicalChannel*>(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:"
<<LOGVAR2("next-chan",mNextChan) <<LOGVAR2("prev-chan",this);
}
mNextChan->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? "<<this;
}
// channel()->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:"<<this;
l3sendm(GSM::L3ChannelRelease(L3RRCause::Normal_Event));
chanSetState(chRequestRelease);
return;
}
if (!mNextChan) {
// Logic error.
LOG(ERR) << "received channel reassignment complete with no nextchan allocated"<<this;
l3sendm(GSM::L3ChannelRelease(L3RRCause::Normal_Event));
chanSetState(chRequestRelease);
return;
}
if (mNextChan->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"<<LOGVARM(mNextChan);
chanSetState(chRequestRelease);
return;
}
mChContext->mmSetChannel(mNextChan);
LOG(INFO) <<"successful channel reassignment" <<LOGVAR2("from-channel",this) <<LOGVAR2("to-channel",mNextChan);
mNextChan = NULL;
} // release lock
// FIXME: There is a race for the new channel to get its ESTABLISH before this old one gets this hardrelease.
sleep(1);
chanSetState(chRequestHardRelease); // Done with this channel.
//chanSetState(L3LogicalChannel::chReassignComplete); // Redundant with sending the HARDRELEASE, this will cause the service loop to exit.
}
#if UNUSED
void L3LogicalChannel::chanLost()
{
LOG(DEBUG)<<this;
// Just in case this gets called with a next channel, release it too, although it would eventually time out on its own.
if (mNextChan) {
mNextChan->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<TranEntry> 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 <<LOGVAR2("nextchan",mNextChan->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<L3LogicalChannel*>(*it);
os << chan;
chan->chanContextText(os);
os << endl;
}
}
}; // namespace