Files
openbts/GSM/GSMLogicalChannel.cpp

964 lines
37 KiB
C++

/**@file Logical Channel. */
/*
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
* Copyright 2010 Kestrel Signal Processing, Inc.
* Copyright 2011, 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.
*/
// 6-2014: Pat Thompson heavily rewrote this.
#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging
// Pretty picture courtesy pat.
//
// +---------------------------------+
// | |
// | Layer3 |
// | |
// +---------------------------------+
// ^ |
// | |
// | v
// +---------------------------------+ +------------------------------------+ +----------------------------+
// | L2LogicalChannel | | SAPMux | | L2LAPDm/SAP0 |
// | l2recv l2sendf/m/p | | | | |
// | | | | | | | |
// | | v | | | | |
// | Queue mL3Out Queue mL3In | | | | |
// | ^ | | | | | |
// | | v | | | | |
// | | MessageServiceLoop->|--->| sapWriteFromL3 ------>X----------->|--->| l2dlWriteHighSide |
// | \ | | | | | |
// | --------------- writeToL3 |<--------------------------------X<-----------| writeL3 |
// | | | | ^ | | |
// | | | | | | | |
// | ------------------------->|--->| sapWriteFromL1 -->X--------------->|--->| l2dlWriteLowSide |
// | / | | | | | | | -> Queue mL1In |
// | | --writeToL1 |<------------------X<-------------------------| writeL1 |
// | | / | | ^ | | | | +----------------------------+
// | | | | | | | | | |
// | | | | | | | | | | +----------------------------+
// | | | | | | | | | | | L2LAPDm/SAP3 |
// | | | | | | | \ | | | |
// | | | | | | | ----------->|--->| l2dlWriteHighSide |
// | | | | | | | \ | | |
// | | | | | | | ------------| writeL3 |
// | | | | | | | | | |
// | | | | | | \ | | |
// | | | | | | --------------->|--->| l2dlWriteLowSide |
// | | | | | \ | | -> Queue mL1In |
// | | | | | --------------------------| writeL1 |
// | | | | | | +----------------------------+
// | writeLowSide v | | |
// +---------------------------------+ +------------------------------------+
// ^ |
// | |
// | v
// +---------------------------------+
// | handleGoodFrame writeHighSide |
// | L1FEC |
// +---------------------------------+
#include "GSML3RRElements.h"
#include "GSML3Message.h"
#include "GSML3RRMessages.h"
#include "GSMSMSCBL3Messages.h"
#include "GSMLogicalChannel.h"
#include "GSMConfig.h"
#include <ControlTransfer.h>
#include <L3Handover.h>
#include "GPRSExport.h"
#include <Globals.h>
#include <Logger.h>
using namespace std;
namespace GSM {
// Tell C++ to put the class vtables here.
void L2LogicalChannel::_define_vtable() {}
void L2LogicalChannelBase::_define_vtable() {}
void SACCHLogicalChannel::_define_vtable() {}
// Comments from David Burgess from wki ticket 1141:
// We are not generating correct idle sequences in L1. Most handsets are not sensitive to this, but some are.
// Unfortunately, one of the most sensitive, the Nokia 1600, is common in some of our target markets.
// As far as I can tell, the correct idle behaviors are:
// for an unused channel on C0 - dummy burst
// for an unused channel on other Cn - dead air, non-transmitting
// for an open-but-idle SDCCH - LAPDm L2 idle frames (an empty unit data frame, see GSM 04.06)
// for an open-but-idle TCH+FACCH prior to activating vocoder - LAPDm L2 idle frames
// for an open-but-idle TCH+FACCH after activating vocoder - silent vocoder frames
// Semaphore works now and reduces cpu% on idle SACCH from 0.3 to negligible, but the thread start/stop logic is pretty weird
// and I am not sure all the cases are handled, so dont enable this.
#define USE_SEMAPHORE 0
void L2LogicalChannelBase::startl1()
{
LOG(DEBUG) <<descriptiveString();
if (mL1) mL1->l1start();
}
void L2SAPMux::flushL3In()
{
while (L3Frame *l3f = mL3In.readNoBlock()) {
if (l3f->primitive() != L3_RELEASE_REQUEST && l3f->primitive() != L3_HARDRELEASE_REQUEST) {
LOG(ERR)<< "channel closure caused message to be discarded:"<<l3f<<" "<<descriptiveString();
}
delete l3f;
}
}
void L2SAPMux::l2stop()
{
LOG(DEBUG) <<descriptiveString();
mL1->l1close();
// Clear the LAPDm input queue.
flushL3In();
// Put the message in the queue and let the service loop serialize the request.
mL3In.write(new L3Frame(SAPI0,L3_HARDRELEASE_REQUEST)); // Reset LAPDm.
mL3In.write(new L3Frame(SAPI3,L3_HARDRELEASE_REQUEST));
}
void L2SAPMux::sapStart()
{
LOG(DEBUG) <<descriptiveString();
devassert(mL1);
startl1();
for (int s=0; s<4; s++) {
// (pat) This starts sending LAPDm idle frames.
if (mL2[s]) mL2[s]->l2dlOpen(descriptiveString());
}
}
void L2LogicalChannelBase::connect(L1FEC *wL1)
{
mL1 = wL1;
if (wL1) wL1->upstream(this);
}
void L2SAPMux::sapInit(L2DL *sap0, L2DL *sap3)
{
LOG(DEBUG);
mL2[0] = sap0; mL2[3] = sap3;
for (int s=0; s<4; s++) {
if (mL2[s]) { mL2[s]->l2Downstream(this); }
}
if (sap0 && sap3) {
dynamic_cast<L2LAPDm*>(sap3)->master(dynamic_cast<L2LAPDm*>(sap0));
}
}
void L2SAPMux::sapWriteFromL1(const L2Frame& frame)
{
OBJLOG(DEBUG) << frame;
unsigned sap;
// (pat) Add switch to validate upstream primitives. The upstream only generates a few primitives;
// the rest are created in L2LAPDm.
switch (frame.primitive()) {
case L2_DATA:
sap = frame.SAPI();
assert(sap == SAPI0 || sap == SAPI3);
if (mL2[sap]) {
mL2[sap]->l2dlWriteLowSide(frame);
} else {
LOG(WARNING) << "received DATA for unsupported"<<LOGVAR(sap);
}
return;
case PH_CONNECT:
// All this does is fully reset LAPDm; copy it out to every SAP.
for (int s = 0; s <= 3; s++) {
if (mL2[s]) mL2[s]->l2dlWriteLowSide(frame); // Note: the frame may have the wrong SAP in it, but LAPDm doesnt care.
}
return;
default:
// If you get this assertion, make SURE you know what will happen upstream to that primitive.
devassert(0);
return; // make g++ happy.
}
}
bool L2SAPMux::multiframeMode(SAPI_t sap) const {
unsigned sapi = SAP2SAPI(sap);
assert(mL2[sapi]);
return mL2[SAP2SAPI(sapi)]->multiframeMode();
}
bool L2LogicalChannel::multiframeMode(SAPI_t sap) const {
if (SAPIsSacch(sap)) {
return getSACCH()->L2SAPMux::multiframeMode(sap);
} else {
return L2SAPMux::multiframeMode(sap);
}
}
LAPDState L2SAPMux::getLapdmState(SAPI_t sap) const { return mL2[SAP2SAPI(sap)] ? mL2[SAP2SAPI(sap)]->getLapdmState() : LAPDStateUnused; }
// For DCCH channels (FACCH, SACCH, SDCCH):
// This function calls virtual L2DL::l2dlWriteHighSide(L3Frame) which maps
// to L2LAPDm::l2dlWriteHighSide() which interprets the primitive, and then
// sends traffic data through sendUFrameUI(L3Frame) which creates an L2Frame
// and sends it through several irrelevant functions to L2LAPDm::writeL1
// which calls (SAPMux)mDownstream->SAPMux::writeHighSide(L2Frame),
// which does nothing but call mL1->writeHighSide(L2Frame), which is a pass-through
// except that the SapMux uses mDownStream which is copied from mL1, so there is a
// chance to redirect it. But wouldn't that be an error?
// Anyway, L1Encoder::writeHighSide is usually overridden.
// For TCH, it goes to XCCHL1Encoder::writeHighSide() which processes
// the L2Frame primitive, then sends traffic data to TCHFACCHL1Encoder::sendFrame(),
// which just enqueues the frame - it does not block.
// A thread runs GSM::TCHFACCHL1EncoderRoutine() which
// calls TCHFACCHL1Encoder::dispatch() which is synchronized with the gBTS clock,
// unsynchronized with the queue, because it must send data no matter what.
// Eventually it encodes the data and
// calls (ARFCNManager*)mDownStream->writeHighSideTx(), which writes to the socket.
//
// From here and below only SAPI0 and SAPI3 are used.
void L2SAPMux::sapWriteFromL3(const L3Frame& frame)
{
LOG(DEBUG) <<LOGVAR(frame);
SAPI_t sap = frame.getSAPI();
if (!(sap == SAPI0 || sap == SAPI3 || sap == SAPI0Sacch || sap == SAPI3Sacch)) {
devassert(0);
sap = SAPI0; // This is a bug fix to avoid a crash.
}
unsigned sapi = SAP2SAPI(sap);
devassert(mL2[sapi]);
if (!mL2[sapi]) { return; } // Not initialized yet? Should never happen.
// On SACCH L3_UNIT_DATA frames block for 480ms.
// L3_DATA frames could block for minutes.
if (mL2[sapi]) mL2[sapi]->l2dlWriteHighSide(frame);
}
// This is the start of a normal or error-caused release procedure, both of which are identical.
// deactivate SACCH means stop transmitting LAPDm frames, so that the handset will time-out based on RADIO_LINK_TIMEOUT
// and release the channel at the RR level.
// On C0 that means to start transmitting dummy frames instead of LAPDm idle frames.
// If you want to stop SACCH immediately, call l2stop() directly so you dont start the mT3109 timer.
void L2LogicalChannel::startNormalRelease()
{
LOG(DEBUG) <<this;
if (!mT3109.active()) { mT3109.set(); }
getSACCH()->l2stop(); // Go to LAPDm 'null' state immediately.
// We stopped SACCH and are dropping the channel, so no more messages should be sent.
// It is the responsibility of layer3 to make sure it does not happen from that direction,
// and if it happens from the handset nothing we can do about it.
flushL3In();
mL3In.write(new L3Frame(SAPI0,L3_RELEASE_REQUEST));
mL3In.write(new L3Frame(SAPI3,L3_RELEASE_REQUEST));
}
void L2LogicalChannel::l2sendf(const L3Frame& frame)
{
SAPI_t sap = frame.getSAPI();
assert(sap == SAPI0 || sap == SAPI3 || sap == SAPI0Sacch || sap == SAPI3Sacch);
WATCHINFO("l2sendf "<<channelDescription() <<LOGVAR(sap) <<LOGVAR(chtype()) <<" " <<frame);
if (SAPIsSacch(sap)) { getSACCH()->l2sendf(frame); return; }
switch (frame.primitive()) {
case L3_ESTABLISH_REQUEST:
if (sap != SAPI3) { LOG(NOTICE) << "unexpected"<<LOGVAR(sap)<<" in "<<LOGVAR(frame); }
break;
case L3_DATA:
case L3_UNIT_DATA:
break;
case L3_RELEASE_REQUEST:
// Normal release initiated by layer3.
if (sap == SAPI0) { // Total deactivation requested.
startNormalRelease();
return;
}
break;
case L3_HARDRELEASE_REQUEST:
// This is a full immediate release.
if (sap == SAPI0) {
immediateRelease();
return;
}
LOG(NOTICE) << "unexpected"<<LOGVAR(sap)<<" in "<<LOGVAR(frame);
break;
default:
assert(0);
return;
}
mL3In.write(new L3Frame(frame));
}
void SACCHLogicalChannel::l2sendf(const L3Frame& frame)
{
SAPI_t sap = frame.getSAPI();
devassert(SAPIsSacch(sap));
if (sap != SAPI3Sacch) { LOG(NOTICE) << "unexpected"<<LOGVAR(sap)<<" in "<<LOGVAR(frame); }
LOG(INFO) <<channelDescription() <<LOGVAR(sap) <<LOGVAR(chtype()) <<" " <<frame;
switch (frame.primitive()) {
case L3_ESTABLISH_REQUEST:
// Fall through.
case L3_DATA:
case L3_UNIT_DATA:
case L3_RELEASE_REQUEST:
case L3_HARDRELEASE_REQUEST:
mL3In.write(new L3Frame(frame));
return;
default:
assert(0);
}
}
L3Frame * L2LogicalChannel::l2recv(unsigned timeout_ms)
{
//LOG(DEBUG);
L3Frame *result = mL3Out.read(timeout_ms);
if (result) WATCHINFO("l2recv " << this <<LOGVAR2("sap",result->getSAPI()) <<LOGVAR(result));
return result;
}
void L2LogicalChannel::writeToL3(L3Frame*frame)
{
LOG(DEBUG) <<this <<LOGVAR(*frame);
switch (frame->primitive()) {
case L3_ESTABLISH_INDICATION:
case L3_ESTABLISH_CONFIRM:
case HANDOVER_ACCESS:
case L3_DATA:
case L3_UNIT_DATA:
break;
case MDL_ERROR_INDICATION:
// Normal release procedure initiated by handset, or due to error at LAPDm level.
// An error is handled identically to a normal release, because the handset may still be listening to us
// even though we lost contact with it, and we want to tell it to release as gracefully as possible
// even though the channel condition may suck.
if (frame->getSAPI() == SAPI0) {
// Release on host chan sap 0 is a total release. We will start the release now.
// FIXME: Are we supposed to wait for any pending SMS requests on SACCH to clear first?
startNormalRelease();
} else {
// We dont kill the whole link for SAP3 release.
// Pass the message on to layer3 to abort whatever transaction is running on SAP3
}
break;
case L3_DATA_CONFIRM: // Sent from LAPDm when data delivered, but we dont care.
WATCHINFO(this <<LOGVAR2("sap",frame->getSAPI()) <<LOGVAR(frame));
delete frame;
return;
case L3_RELEASE_INDICATION:
case L3_RELEASE_CONFIRM: // Sent from LAPDm when link release is confirmed, but we dont care.
if (frame->getSAPI() == SAPI0) {
mT3109.reset();
mT3111.set();
}
break;
default:
assert(0);
}
mL3Out.write(frame);
}
void SACCHLogicalChannel::writeToL3(L3Frame*frame)
{
switch (frame->primitive()) {
case L3_DATA:
case L3_UNIT_DATA:
if (processMeasurementReport(frame)) { return; }
// Fall Through...
case L3_ESTABLISH_CONFIRM:
case L3_ESTABLISH_INDICATION:
// The uplink message queue resides in L2LogicalChannel
mHost->writeToL3(frame); // TODO: frame should include channel indicator, but l3 doesnt care. Would be nice for debugging.
return;
case L3_RELEASE_INDICATION:
case MDL_ERROR_INDICATION:
frame->mSapi = (SAPI_t) (frame->mSapi | SAPChannelFlag);
mHost->writeToL3(frame); // FIXME: frame should include channel indicator.
return;
case L3_DATA_CONFIRM: // Sent from LAPDm when data delivered, but we dont care.
case L3_RELEASE_CONFIRM: // Sent from LAPDm when link release is confirmed, but we dont care.
delete frame; // We dont do anything with this; we are releasing regardless of whether we get a confirm or not.
return;
default:
assert(0);
}
}
// FIXME: It blocks until L2LAPDm::sendIdle returns. That does not need to block.
// Other channels call it too, which is somewhat nonsensical; the l2open for other channels is empty.
// There is no close in L2 - the L1 encoder/decoder are closed individually when L2 sends a RELEASE/HARDRELEASE primitive.
void L2LogicalChannel::lcstart()
{
LOG(DEBUG) <<this;
devassert(mSACCH);
if (mSACCH) mSACCH->sapStart();
sapStart();
mL3Out.clear();
mT3101.set(T3101ms);
mT3109.reset(gConfig.GSM.Timer.T3109); // redundant with init in lcinit but cant be too careful.
mT3111.reset(T3111ms); // redundant with init in lcinit but cant be too careful.
}
// (pat) For data channels this is called by getTCH or getSDCCH.
// TODO: Go through all the getTCH/getSDCCH users and make sure they lcstart.
void L2LogicalChannel::lcinit()
{
LOG(DEBUG) <<this;
assert(mL1);
if (mL1) mL1->l1init(); // (pat) L1FEC::l1init()
devassert(mSACCH);
if (mSACCH) mSACCH->sacchInit();
// We set T3101 now so the channel will become recyclable if the caller does nothing with it;
// the caller has this long to call lcstart before the channel goes back to the recyclable pool.
mT3101.set(T3101ms); // It will be started again in l2start.
mT3109.reset(gConfig.GSM.Timer.T3109);
mT3111.reset(T3111ms);
//mTRecycle.reset(500); // (pat) Must set a dummy value.
if (!mlcMessageLoopRunning) {
mlcMessageLoopRunning=true;
mlcMessageServiceThread.start2((void*(*)(void*))MessageServiceLoop,this,8000*sizeof(void*));
}
if (!mlcControlLoopRunning) {
mlcControlLoopRunning=true;
mlcControlServiceThread.start2((void*(*)(void*))ControlServiceLoop,this,8000*sizeof(void*));
}
}
void L2LogicalChannel::lcopen()
{
LOG(INFO) <<"open channel "<<this;
lcinit();
lcstart();
}
// old TODO: We want to call L2LAPDm::abnormalRelease but it blocks, so we really need to send that thread a message.
// GSM 5.08 6.7.1: The MS may monitor TCH-SACCH or SDCCH-SACCH instead of BCCH on surrounding cells
// to speed up cell reselection process. That implies that the SACCH are running constantly!
// "The received signal level measurements on surrounding cells made during the last 5 seconds on the TCH
// or SDCCH may be averaged and used, where possible, to speed up the process. However, it should be
// noted that the received signal level monitoring while on the TCH or SDCCH is on carriers in BA
// (SACCH), while the carriers to be monitored for cell reselection are in BA (BCCH) or BA (GPRS)."
bool L2LogicalChannel::recyclable()
{
//return mTRecycle.expired();
return (mT3101.expired() || mT3109.expired() || mT3111.expired()) && mL1->encoder()->l1IsIdle() && getSACCH()->mL1->encoder()->l1IsIdle();
}
// We know this channel is now unused. Finish deactivating it and mark it for reuse.
// The SACCH was already deactivated. Just close the main channel and become recyclable.
void L2LogicalChannel::immediateRelease()
{
LOG(DEBUG) <<this;
mT3101.reset();
mT3109.reset();
mT3111.expire(); // make sure the channel becomes recyclable
#if 0 // fixed a better way
// (pat) When we release the channel the LAPDm state machine sends a dummy frame on the channel.
// We dont want to try to reuse the channel until that clears.
// FIXME: Do this better - go into the encoder and save the time when dummy frame is sent.
if (getSACCH()->l1active()) {
// We dont need the delay if this is a normal release, meaning the sacch was deactivated a long time ago.
mTRecycle.set(500); // (pat) Channel will be recyclable when this expires. SACCH is 480ms. Could actually be up to 1 second.
} else {
mTRecycle.expire(); // Recycle now.
}
#endif
getSACCH()->l2stop(); // Done already in the T3109 or T3111 expiry cases.
this->l2stop();
}
// Service the main SDCCH or TCH/FACCH L2LogicalChannel.
// We are just looking for released channels, which is timer based and so has to be checked from a service thread.
// Called from the service loop for the SACCH, because that is where the thread lives.
// WARNING: Runs in a different thread than everything else which runs in the LAPDm service handler thread.
void L2LogicalChannel::serviceHost()
{
LOG(DEBUG);
// We do not test T3101 on SACCH because sometimes the first measurement report does not
// arrive in time to keep T3101 from expiring there.
if (mT3101.expired()) {
// Failure of MS to seize channel.
// Layer3 has not been started, so all we do is close down LAPDm immediately.
LOG(INFO) <<this <<" channel closed on T3101 expiry";
immediateRelease();
} else if (mT3109.expired()) {
// The handset never responded to the LAPDm DISC, but we have waited long enough for it to disconnect.
LOG(INFO) <<this <<" channel closed on T3109 expiry";
immediateRelease();
} else if (mT3111.expired()) {
// Happiest outcome. The LAPDm disconnected with a full disconnect handshake.
LOG(INFO) <<this <<" channel closed on T3109 expiry";
immediateRelease();
}
}
void *L2LogicalChannel::ControlServiceLoop(L2LogicalChannel* hostchan)
{
while (!gBTS.btsShutdown()) {
sleepFrames(26); // We dont have to do this very often.
if (hostchan->l1active()) {
hostchan->serviceHost();
}
// The SACCH is deactivated before the host channel, so we check l1active to see if SACCH is still running.
SACCHLogicalChannel *sacch = hostchan->getSACCH();
if (sacch->l1active() && sacch->sacchRadioFailure()) {
// GSM 4.08 3.4.13.2: layer2 is supposed to inform layer3 directly, bypassing LAPDm.
// The layer3 response is identical to a normal RELEASE from layer3: deactivate the SACCH,
// start T3109, recycle the channel when it expires.
// Just because we cannot hear the MS on SACCH does not mean that it cannot hear it, or that
// we have completely lost contact, so LAPDm can go ahead with the normal release procedure,
// ie, send a DISC on the main link and wait for a response - if we get it we can use T3111.
hostchan->startNormalRelease();
}
}
//hostchan->mlcControlLoopRunning=false;
return NULL;
}
// This drives messages from layer3 down through LAPDm, layer1, and all the way to the radio.
void *L2LogicalChannel::MessageServiceLoop(L2LogicalChannel* hostchan)
{
WATCHINFO("Starting MessageServiceLoop for "<<hostchan);
while (!gBTS.btsShutdown()) {
L3Frame *l3fp = hostchan->mL3In.read(52*4); // Add a delay so will exit at BTS shutdown.
// (pat) The frames may be L3_DATA which will block until delivered, which could take minutes,
// which is why we need a separate thread to drive this.
if (l3fp) {
hostchan->sapWriteFromL3(*l3fp);
delete l3fp;
}
}
//hostchan->mlcMessageLoopRunning = false;
return NULL;
}
// Return true if the phy link is either off or failed. Those are the conditions under which Layer3 should punt.
// GSM 5.08 5.3: Radio link failure in the BSS is based on the error rate in the uplink SACCH or
// RXLEV/RXQUAL measurements of the MS. We use only the former.
bool L2LogicalChannel::radioFailure() const {
return !l1active() || getSACCH()->sacchRadioFailure();
}
// (pat) This is the primary way of detecting loss of contact with a handset.
bool SACCHLogicalChannel::sacchRadioFailure() const
{
return mSACCHL1->decoder()->mBadFrameTracker > gConfig.GSM.BTS.RADIO_LINK_TIMEOUT;
}
void L2LogicalChannelBase::downstream(ARFCNManager* radio)
{
assert(mL1); // This is L1FEC
mL1->downstream(radio);
}
// (pat) This is only called during initialization, using the createCombination*() functions.
// The L1FEC->downstream hooks the radio to this logical channel, permanently.
void L2LogicalChannel::downstream(ARFCNManager* radio)
{
mL1->downstream(radio);
if (mSACCH) mSACCH->mL1->downstream(radio);
}
// Serialize and send an L3Message with a given primitive.
// The msg is not deleted; its value is used before return.
void L2LogicalChannelBase::l2sendm(const L3Message& msg, GSM::Primitive prim, SAPI_t SAPI)
{
//OBJLOG(INFO) << "L3" <<LOGVAR(SAPI) << " sending " << msg;
WATCHINFO("l2sendm "<<this <<LOGVAR(SAPI) << " sending " << msg);
l2sendf(L3Frame(msg,prim,SAPI));
}
void L2LogicalChannel::writeLowSide(const L2Frame& frame)
{
// If this is the first good frame of a new transaction,
// stop T3101 and tell L2 we're alive down here.
if (mT3101.active()) {
mT3101.reset();
// Inform L3 that we are alive down here.
// This does not block; goes to a InterthreadQueue L2LAPdm::mL1In
sapWriteFromL1(L2Frame(PH_CONNECT));
}
switch (frame.primitive()) {
case HANDOVER_ACCESS: // Only send this on SAPI 0.
writeToL3(new L3Frame(SAPI0,HANDOVER_ACCESS));
break;
default:
sapWriteFromL1(frame);
break;
}
}
// This frame comes down from L2LAPDm::writeL1.
// RELEASE: Normal release request from Layer3, including imsi detach, or lost radio contact.
// deactivate SACCH, leave main L2LogicalChannel going, wait T3109
// You would think that if layer3 sent an l3 channel release, we could release the link sooner,
// but there is no guarantee that l3 message got through yet, so we have to wait for the L2 LAPDm DISC indication
// before we can switch to T3111.
// GSM 4.08 3.4.13.1.1 NOTE 1: Layer3 Channel Release on the main signaling link causes all other signaling links
// to be terminated by "local end release" which 4.06 5.4.4 defines as immediately entering idle state, wherein
// LAPDm can respond to handshakes but send nothing new.
// Also 24.011 (SMS) 2.3: SAP3 may be released explicitly (LAPDm DISC) or implicitly by channel release.
// I infer, therefore, that is the job of Layer3 to make that sure that the other links (main-SAP3 and SACCH-SAP0,SAP3) are
// released before the main link, in other words, to make sure SMS or RR (eg, RRLP) messages are finished.
// Question: is it necessary to send RELEASE primitives on those other links to terminate multi-frame-mode?
// I would think so, because otherwise the handset could initiate a new multi-frame-mode command while the ChannelRelease
// from the BTS is in flight on the main link.
// btw, SACCH-SAP3 may be is used for SMS, but I dont know what SACCH-SAP0 would ever be used for.
// HARDRELEASE:
// Handover failure, or post-successful channel reassignment.
void L2LogicalChannel::writeToL1(const L2Frame& frame)
{
// The SAP may or may not be present, depending on the channel type.
OBJLOG(DEBUG) << frame;
switch (frame.primitive()) {
case L2_DATA:
mL1->writeHighSide(frame);
break;
default:
OBJLOG(ERR) << "unhandled primitive " << frame.primitive() << " in L2->L1";
devassert(0);
}
}
void SACCHLogicalChannel::writeToL1(const L2Frame& frame)
{
// The SAP may or may not be present, depending on the channel type.
OBJLOG(DEBUG) << frame;
switch (frame.primitive()) {
case L2_DATA:
mL1->writeHighSide(frame);
break;
default:
OBJLOG(ERR) << "unhandled primitive " << frame.primitive() << " in L2->L1";
devassert(0);
}
}
L3ChannelDescription L2LogicalChannelBase::channelDescription() const
{
// In some debug cases, L1 may not exist, so we fake this information.
if (mL1==NULL) return L3ChannelDescription(TDMA_MISC,0,0,0);
// In normal cases, we get this information from L1.
return L3ChannelDescription(
mL1->typeAndOffset(),
mL1->TN(),
mL1->TSC(),
mL1->ARFCN()
);
}
ChannelHistory *L2LogicalChannel::getChannelHistory()
{
return mSACCH ? mSACCH->getChannelHistory() : NULL;
}
string L2LogicalChannel::displayTimers() const
{
ostringstream ss;
ss <<LOGVARM(mT3101) <<LOGVARM(mT3109) <<LOGVARM(mT3111);
if (mSACCH) {
// The only thing we want to display from L1 is the bad-frame-tracker variable, which is only useful in SACCH.
ss <<mSACCH->mL1->displayTimers();
}
return ss.str();
}
SDCCHLogicalChannel::SDCCHLogicalChannel(
unsigned wCN,
unsigned wTN,
const CompleteMapping& wMapping)
{
mL1 = new SDCCHL1FEC(wCN,wTN,wMapping.LCH());
// SAP0 is RR/MM/CC, SAP3 is SMS
// SAP1 and SAP2 are not used.
L2LAPDm *sap0 = new SDCCHL2(1,SAPI0); // derived from L2LAPDm
L2LAPDm *sap3 = new SDCCHL2(1,SAPI3);
LOG(DEBUG) << "LAPDm pairs SAP0=" << sap0 << " SAP3=" << sap3;
sapInit(sap0,sap3);
mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH(),this);
connect(mL1);
}
SACCHLogicalChannel::SACCHLogicalChannel(
unsigned wCN,
unsigned wTN,
const MappingPair& wMapping,
/*const*/ L2LogicalChannel *wHost)
: mHost(wHost)
{
mSACCHL1 = new SACCHL1FEC(wCN,wTN,wMapping);
mL1 = mSACCHL1;
// SAP0 is RR, SAP3 is SMS
// SAP1 and SAP2 are not used.
L2LAPDm *sap0 = new SACCHL2(1,SAPI0); // derived from L2LAPDm
L2LAPDm *sap3 = new SACCHL2(1,SAPI3);
sapInit(sap0,sap3);
connect(mL1);
//assert(mSACCH==NULL);
#if USE_SEMAPHORE
int sval, semstat= sem_getvalue(&mOpenSignal,&sval);
LOG(DEBUG) << descriptiveString() << " SEM_INIT " <<LOGVAR(semstat) <<LOGVAR(sval);
sem_init(&mOpenSignal,0,0);
#endif
}
void SACCHLogicalChannel::sacchInit()
{
LOG(DEBUG) <<this;
if (mL1) mL1->l1init(); // (pat) L1FEC::l1init()
neighborClearMeasurements();
//mAverageRXLEV_SUB_SERVICING_CELL = 0;
// Just make sure any stray messages are flushed when we reactivate the channel.
while (L3Message *straymsg = mTxQueue.readNoBlock()) { delete straymsg; }
mMeasurementResults = L3MeasurementResults(); // clear it
#if USE_SEMAPHORE
//cout << descriptiveString() << " POST " <<sem_getvalue(&mOpenSignal,&sval) <<LOGVAR(sval) <<endl;
int sval, semstat= sem_getvalue(&mOpenSignal,&sval);
LOG(DEBUG) << descriptiveString() << " SEM_POST " <<LOGVAR(semstat) <<LOGVAR(sval);
sem_post(&mOpenSignal); // Note: you must open the L2LogicalChannel before starting the SACCH service loop.
#endif
if (!mSacchRunning) {
mSacchRunning=true;
// This thread pushes data through lapdm and all the way to L1Encoder.
mSacchServiceThread.start2((void*(*)(void*))SACCHServiceLoop,this,12000*sizeof(void*));
}
LOG(DEBUG);
}
static L3Message* parseSACCHMessage(const L3Frame *l3frame)
{
if (!l3frame) return NULL;
LOG(DEBUG) << *l3frame;
Primitive prim = l3frame->primitive();
if ((prim!=L3_DATA) && (prim!=L3_UNIT_DATA)) {
LOG(INFO) << "non-data primitive " << prim;
return NULL;
}
L3Message* message = parseL3(*l3frame);
if (!message) {
LOG(WARNING) << "SACCH received unparsable L3 frame " << *l3frame;
WATCHF("SACCH received unparsable L3 frame PD=%d MTI=%d",l3frame->PD(),l3frame->MTI());
}
return message;
}
bool SACCHLogicalChannel::processMeasurementReport(L3Frame *rrFrame)
{
if (! (rrFrame->isData() && rrFrame->PD() == L3RadioResourcePD && rrFrame->MTI() == L3RRMessage::MeasurementReport)) { return false; }
// Neither of these 'ifs' should fail, but be safe.
if (const L3Message* rrMessage = parseSACCHMessage(rrFrame)) {
if (const L3MeasurementReport* measurement = dynamic_cast<typeof(measurement)>(rrMessage)) {
OBJLOG(INFO) << "SACCH measurement report " <<this <<" "<< mMeasurementResults;
mMeasurementResults = measurement->results();
//if (mMeasurementResults.MEAS_VALID() == 0) {
// addSelfRxLev(mMeasurementResults.RXLEV_SUB_SERVING_CELL_dBm());
//}
// Add the measurement results to the sql table (pat - no longer used)
// Note that the typeAndOffset of a SACCH match the host channel.
gPhysStatus.setPhysical(this, mMeasurementResults);
// Check for handover requirement.
// (pat) TODO: This may block while waiting for a reply from a Peer BTS.
Control::HandoverDetermination(&mMeasurementResults,this);
}
delete rrMessage;
}
delete rrFrame;
return true;
}
// This sends Layer3 messages into the high side of LAPDm.
// This blocks until a message is sent.
// Routine relies on l2sendf passing directly through LAPDm and going all the way to L1Encoder::transmit, which calls waitToSend.
void SACCHLogicalChannel::serviceSACCH(unsigned &count)
{
LOG(DEBUG);
// Send any outbound messages. If the tx queue is empty send alternating SI5/6.
if (L3Frame *l3fp = mL3In.readNoBlock()) {
// We are writing on SAPI0 (instead of SAPI0Sacch), which is ok. At the SAP layer it is just SAPI0 or SAPI3
sapWriteFromL3(*l3fp);
delete l3fp;
} else {
// Send alternating SI5/SI6.
// These L3Frames were created with the UNIT_DATA primivitive.
// (pat) blocks using waitToSend until L1Encoder::mPrevWriteTime
OBJLOG(DEBUG) << "sending SI5/6 on SACCH";
if (count%2) sapWriteFromL3(gBTS.SI5Frame());
else sapWriteFromL3(gBTS.SI6Frame());
count++;
}
// RSSIBumpDown moved to SACCHL1Decoder::countBadFrame();
}
// (pat) This is started when SACCH is opened, and runs forever.
// The SACCHLogicalChannel are created by the SDCCHLogicalChannel and TCHFACCHLogicalChannel constructors.
void *SACCHLogicalChannel::SACCHServiceLoop(SACCHLogicalChannel* sacch)
{
WATCHINFO("Starting SACCHServiceLoop for "<<sacch);
unsigned count = 0;
while (!gBTS.btsShutdown()) {
//if (gBTS.time().FN() % 104 == 0) { OBJLOG(DEBUG) <<LOGVAR(l1active()) <<LOGVAR(recyclable()); }
// Throttle back if not active.
// This is equivalent to testing if the L2LAPDm is in 'null' state as defined in GSM 4.06.
// In null state we send dummy frames; in L2LAPDm idle state we send L2 idle frames.
// The dummy frame is sent down to the transceiver when L2LAPDm is closed.
if (! sacch->l1active()) {
// pat 5-2013: Vastly reducing the delays here and in L2LAPDm to try to reduce
// random failures of handover and channel reassignment from SDCCH to TCHF.
// Update: The further this sleep is reduced, the more reliable handover becomes.
// I left it at 4 for a while but handover still failed sometimes.
//sleepFrames(51);
#if USE_SEMAPHORE
sleepFrames(51); // In case the semaphore does not work.
// (pat) Update: Getting rid of the sleep entirely. We will use a semaphore instead.
// Note that the semaphore call may return on signal, which is ok here.
int sval, semstat= sem_getvalue(&mOpenSignal,&sval);
//cout << descriptiveString() << " WAIT " <<sem_getvalue(&mOpenSignal,&sval) <<LOGVAR(sval) <<endl;
LOG(DEBUG) << descriptiveString() << " SEM_WAIT " <<LOGVAR(semstat) <<LOGVAR(sval);
sem_wait(&mOpenSignal);
// sem_post sem_overview
semstat= sem_getvalue(&mOpenSignal,&sval);
//cout << descriptiveString() << " AFTER " <<LOGVAR(semstat) <<LOGVAR(sval) <<endl;
LOG(DEBUG) << descriptiveString() << " SEM_AFTER " <<sem_getvalue(&mOpenSignal,&sval) <<LOGVAR(sval);
#else
// (pat 3-2014) Changing this sleepFrames(51) to sleepFrames(2) had an enormous impact
// on idle cpu utilization, from 11.2% to 8.5% with a C-5 beacon, ie, with only 4 SACCH running.
// TODO: Can use fewer CPU cycles by using something like waitToSend to sleep until the next SACCH transmission time.
sleepFrames(1);
#endif
// A clever way to avoid the sleep above would be to wait for ESTABLISH primitive.
// (But which do you wait on - the tx or the rx queue?
continue; // paranoid, check again.
}
// (pat 4-2014) Added to detect RR failure. This is needed because a Layer3 MMContext is not allocated
// until the first L3 message arrives. If the channel fails without sending an L3 message we would never
// notice unless we are watching these RR level timers.
// Close the host channel, which will also close this SACCH.
sacch->serviceSACCH(count);
}
//sacch->mSacchRunning = false; dont think we would restart this one; would alloc a new one.
return NULL;
}
// These have to go into the .cpp file to prevent an illegal forward reference.
void L2LogicalChannel::l1InitPhy(float wRSSI, float wTimingError, double wTimestamp)
{ assert(mSACCH); mSACCH->l1InitPhy(wRSSI,wTimingError,wTimestamp); }
void L2LogicalChannel::setPhy(const L2LogicalChannel& other)
{ assert(mSACCH); mSACCH->setPhy(*other.mSACCH); }
MSPhysReportInfo * L2LogicalChannel::getPhysInfo() const {
assert(mSACCH); return mSACCH->getPhysInfo();
}
const L3MeasurementResults& L2LogicalChannel::measurementResults() const
{ assert(mSACCH); return mSACCH->measurementResults(); }
TCHFACCHLogicalChannel::TCHFACCHLogicalChannel(
unsigned wCN,
unsigned wTN,
const CompleteMapping& wMapping)
{
mTCHL1 = new TCHFACCHL1FEC(wCN,wTN,wMapping.LCH());
mL1 = mTCHL1;
// SAP0 is RR/MM/CC, SAP3 is SMS
// SAP1 and SAP2 are not used.
L2LAPDm *sap0 = new FACCHL2(1,SAPI0);
L2LAPDm *sap3 = new FACCHL2(1,SAPI3);
sapInit(sap0,sap3);
mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH(),this);
connect(mL1);
}
CBCHLogicalChannel::CBCHLogicalChannel(int wCN, int wTN, const CompleteMapping& wMapping)
{
mL1 = new CBCHL1FEC(wCN, wTN, wMapping.LCH());
L2DL *sap0 = new CBCHL2;
sapInit(sap0,NULL);
mSACCH = new SACCHLogicalChannel(wCN,wTN,wMapping.SACCH(),this);
connect(mL1);
}
void CBCHLogicalChannel::l2sendm(const L3SMSCBMessage& msg)
{
L3Frame frame(L3_UNIT_DATA,88*8);
msg.write(frame);
l2sendf(frame);
}
void CBCHLogicalChannel::l2sendf(const L3Frame& frame)
{
if (mL2[0]) { // Should always be set, but protects against a race during startup.
mL2[0]->l2dlWriteHighSide(frame);
}
}
void CBCHLogicalChannel::cbchOpen()
{
if (mL1) mL1->l1init(); // (pat) L1FEC::l1init()
sapStart(); // startl1();
devassert(mSACCH);
if (mSACCH) {
mSACCH->sacchInit();
mSACCH->sapStart();
}
}
ostream& operator<<(ostream& os, const L2LogicalChannelBase& chan)
{
os << chan.descriptiveString();
return os;
}
std::ostream& operator<<(std::ostream&os, const L2LogicalChannelBase*ch)
{
if (ch) { os <<*ch; } else { os << "(null L2Logicalchannel)"; }
return os;
}
ostream& operator<<(ostream& os, const L2LogicalChannel& chan) { return operator<<(os,(L2LogicalChannelBase&)chan); }
ostream& operator<<(ostream& os, const L2LogicalChannel* chan) { return operator<<(os,(L2LogicalChannelBase*)chan); }
};
// vim: ts=4 sw=4