/**@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 #include #include "GPRSExport.h" #include #include 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) <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:"<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) <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(sap3)->master(dynamic_cast(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"<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) <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) <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 "<l2sendf(frame); return; } switch (frame.primitive()) { case L3_ESTABLISH_REQUEST: if (sap != SAPI3) { LOG(NOTICE) << "unexpected"<getSAPI()) <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 <getSAPI()) <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) <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) <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 "<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) <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) <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 "<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" <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 <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 " <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 " <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(rrMessage)) { OBJLOG(INFO) << "SACCH measurement report " <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 "<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 " <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