/* * 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. */ #define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging #define TBF_IMPLEMENTATION 1 #include "MSInfo.h" #include "TBF.h" #include "FEC.h" #include "RLCMessages.h" #include "BSSG.h" #include "RLCEngine.h" #include #include using namespace Control; namespace GPRS { typedef SGSN::GprsSgsnDownlinkPdu DownlinkQPdu; static bool SendExtraTA = 0;// DEBUG: Send an extra TA message // after an Immediate Assignment just to prove it is working. // 4-24-2012, turned it back off, does not help CS-4 bug. static bool T3168Behavior = 1; // Dont send downlink assignments while t3168 running. static int configTbfRetry() { return gConfig.getNum("GPRS.TBF.Retry"); } bool MsgTransaction::mtMsgPending(MsgTransactionType mttype) { ScopedLock lock(mtLock); // The multiple tests here are overkill. return mtMsgExpectedBits.isSet(mttype) && mtExpectedAckBSN[mttype].valid() && mtExpectedAckBSN[mttype] + BSNLagTime >= gBSNNext; } // Is any message pending? bool MsgTransaction::mtMsgPending() { ScopedLock lock(mtLock); // We are not 'waiting' until the expectedAckBSN is valid, because: // 1. the first time through the TBF state machine's logic, // it tests this function before the msgAckTime has been set. // 2. In RLCEngine.cpp, we wait on an optional RRBP reservation, // so we dont want to wait forever if the first one does not get // its reservation and expectedAckBSN is still invalid. //GPRSLOG(1) << "mtMsgPending:" <mtGetState() == TBFState::DataFinal) { // In this state this uplink TBF has already completed and we have sent the // uplinkAckNack message. We now set the TBF_EST field in that message // so the MS may respond to the RRBP reservation with a PacketResourceRequest. // Doesn't really matter whether that is the case or not - in this state, // go ahead and start a new uplink TBF. } else if (activeTbf->isTransmitting()) { // When the MS requests a second TBF when one is already running, // it means it has a new PDU with a different priority or QoS (throughput.) // See 44.060 8.1.1.1.2 and 9.5. // We are required to reissue the TBF assignment within timer T3168. // Originally I just sent a new PacketUplinkAssignment, but that does not always, // we have to end the TBF and restart it. // Maybe if the new priority/QoS is lower, then just reissuing a new // PacketUplinkAssignment on the current TBF would work, but I'm just // going to reissue the TBF. // This code waits until the current PDU finishes and then terminates the transfer, // which implies that we expect the MS to comply by setting the uplink MAC header Countdown Value to 0 // within the T3168 grace period. // FIXME: Temporarily provide a way to disable this in case it has a bug: if (gConfig.getBool("GPRS.Reassign.Enable")) { activeTbf->mtPerformReassign = true; } return NULL; } else { // The activeTbf is in the midst of being issued a new assignment. if (tlli != activeTbf->mtTlli) { // The new uplink tbf has a different tlli. // This case happens only for the AttachComplete message when it is // sent by the MS in an uplink tbf requested in the downlinkacknack // for the AttachAccept message. So check for this special case: if (tlli != ms->msTlli && tlli != ms->msOldTlli) { // Should not happen because the internal SGSN provides // the newTlli as an alias with the AttachAccept message. GLOG(ERR) << "uplink TBF request"<mtTlli = tlli; } else { // Just ignore this duplicate uplink request. // TODO: We can infer the MS mode if this arrives on RACH. GPRSLOG(1) << "MS denied second uplink TBF" << activeTbf; } return NULL; } } TBF *tbf = (TBF*) new RLCUpEngine(ms,mCRD.mRLCOctetCount); tbf->mtTlli = tlli; tbf->mtUnAckMode = mCRD.mRLCMode; if (mCRD.mLLCPDUType == 0) { // 0 == LLC PDU is SACK or ACK, 1 == its not GLOG(ERR) << "Uplink PDU LLC SACK request"<mtSetState(TBFState::DataReadyToConnect); return tbf; } ChannelCodingType TBF::mtChannelCoding() const { // Return 0 - 3 for CS-1 or CS-4 for data transfer. assert(mtChannelCodingMax >= ChannelCodingCS1 && mtChannelCodingMax <= ChannelCodingCS4); ChannelCodingType result; if (mtChannelCodingMax == ChannelCodingCS1) { result = ChannelCodingCS1; // Locked to lowest speed. No need to query the MS RSSI. } else { ChannelCodingType dynamicCS = mtMS->msGetChannelCoding(mtDir); result = min(mtChannelCodingMax,dynamicCS); } mtMS->msChannelCoding.addPoint((int)result); return result; } TBF::TBF(MSInfo *wms, RLCDirType wdir) : mtState(TBFState::Unused), mtDebugId(++Stats.countTBF), mtMS(wms), mtDir(wdir), mtTFI(-1) { gReports.incr("GPRS.TBF"); RN_MEMCHKNEW(TBF) mtChannelCodingMax = ChannelCodingMax; // This may be changed by caller. mtCCMin = mtCCMax = (ChannelCodingType)-1; gL2MAC.macAddTBF(this); mtMS->msAddTBF(this); // Reset these counters to avoid killing the TBF before it has a chance to do anything: mtMS->msTalkUpTime.setNow(); mtMS->msTalkDownTime.setNow(); mtMS->msCountTbfs++; // Reset these counts just to validate them. //mtPersistLastUse.setNow(); //mtPersistKeepAlive.setNow(); } TBF::~TBF() { RN_MEMCHKDEL(TBF) } // housekeeping handled by mtDelete() const char *TBF::tbfid(bool verbose) { static char buf[30]; int n1 = sprintf(buf, "%c%d", (mtDir==RLCDir::Up ? 'U' : 'D'), mtDebugId); if (verbose) { // Just add the seconds part of the time string, which looks like "HH:MM:SS.T". const char *ts = strrchr(timestr().c_str(),':'); if (ts) { n1 += sprintf(buf+n1," %s",ts+1); } // Add the TLLI. //sprintf(buf+n1," MS#%d,%x",mtMS->msDebugId,mtMS->msTlli); sprintf(buf+n1," MS#%d",mtMS->msDebugId); } return (const char *)buf; } //void TBF::setRadData(RadData &wRD) //{ // mtMS->msSetRadData(wRD); // if (wRD.mRSSI < mtLowRSSI) { mtLowRSSI = wRD.mRSSI; } //} void TBF::mtDelete(bool forever) { devassert(! mtAttached); //mtDetach(); // Did this already, but be safe and call again. mtMS->msForgetTBF(this); gL2MAC.macForgetTBF(this,forever); // TBF destruction happens in here. } void MsgTransaction::text(std::ostream &os) const { os << LOGHEX(mtMsgExpectedBits); os << LOGHEX(mtMsgAckBits); for (int i = 0; i < MsgTransMax; i++) { if (mtExpectedAckBSN[i].valid() && mtExpectedAckBSN[i] + BSNLagTime >= gBSNNext) { os << " mtExpectedAckBSN["<msPacch; //if (mtDir == RLCDir::Up && pacch) { // tn = pacch->TN(); // usf = (tn >= 0) ? (int)mtMS->msUSFs[tn] : -1; //} // os << LOGVAR(pacch) << LOGVAR(usf); os << this // Dumps the operator<< value, which is sprintf(TBF#%d,mtDebugId) << LOGVAR(mtMS) //<< " mtDir=" << RLCDir::name(mtDir) << LOGVAR(mtDir) << "\n"; os << "\t"; mtMS->msDumpChannels(os); os << "\n"; if (0) { PDCHL1Uplink *up; PDCHL1Downlink *down; os << "\t"; RN_FOR_ALL(PDCHL1DownlinkList_t,mtMS->msPCHDowns,down) { os << format(" down%d:%d",down->ARFCN(),down->TN()); } RN_FOR_ALL(PDCHL1UplinkList_t,mtMS->msPCHUps,up) { int tn = up->TN(); os << format(" up%d:%d usf=%d",up->ARFCN(),tn,(int)mtMS->msUSFs[tn]); } } os << "\t" << LOGVAR2("mtState=",TBFState::name(mtState)) << LOGVAR(mtAttached) << LOGVAR(mtTFI) << LOGHEX(mtTlli); if (mtDir == RLCDir::Down) { os <<" size="<msN3101) os << LOGVAR2("N3101",mtMS->msN3101); if (mtN3103) os << LOGVAR2("N3103",mtN3103); if (mtN3105) os << LOGVAR2("N3105",mtN3105); if (mtDeadTime.valid()) os << LOGVAR2("deadTime",- mtDeadTime.elapsed()); if (mtMS->msT3193.active()) { os<msT3193.remaining()); } if (mtMS->msT3168.active()) { os<msT3168.remaining()); } if (mtDescription.size()) os <<" descr="<msDumpCommon(os); mtMS->dumpSignalQuality(os); os << "\t"; engineDump(os); unsigned total, unique, grants; engineGetStats(&total,&unique,&grants); // Note that this statistic is off by the small number of blocks // that have been sent but not yet acknowledged. os << "\n\t blocks:" << LOGVAR(total) << LOGVAR(unique) << LOGVAR(grants); //float efficiency = 1.0 * slotsused / slotstotal; // os << LOGVAR(efficiency); //os << "\n"; return os.str(); } static void tbfDumpAll() { if (GPRSDebug & 2) { GPRSLOG(2) <<"TBF DUMP:\n"; TBF *tbf; RN_MAC_FOR_ALL_TBF(tbf) { GPRSLOG(2) << tbf->tbfDump(true) << "\n"; } } } // Can never have too many const in a language, thats what I always say. RLCDownEngine *TBF::getDownEngine() { return (mtDir == RLCDir::Down) ? dynamic_cast(this) : NULL; } //RLCDownEngine const *TBF::getDownEngine() const //{ // return (mtDir == RLCDir::Down) ? dynamic_cast(this) : NULL; //} // Release resources for this TBF: TFI, and if we were the last user of this USF, // release that as well. // Note this code is overkill at the moment, because there is only // one tfi list shared among all channels. // This does not release the channel assignment. void TBF::mtDetach() { if (mtAttached) { mtAttached = false; // Must do this before calling msCleanUSFs() // Set the state to the transitory Deleting state, so that the callers // who wade through the TBF lists will ignore this one now. mtSetState(TBFState::Deleting); mtFreeTFI(); if (mtDir == RLCDir::Up) { mtMS->msCleanUSFs(); } } } // TODO: The TBF needs to detach and then re-attach. void TBF::mtDeReattach() { if (mtAttached) { mtAttached = false; // Must do this before calling msCleanUSFs() mtSetState(TBFState::DataReadyToConnect); mtFreeTFI(); if (mtDir == RLCDir::Up) { mtMS->msCleanUSFs(); } } } uint32_t TBF::mtGetTlli() { uint32_t tlli = mtTlli; if (gFixConvertForeignTLLI) { if ((tlli & 0xc0000000) == 0x80000000) { // Is it a foreign tlli? tlli |= TLLI_LOCAL_BIT; GPRSLOG(1) << "*** Converting foreign tlli to local:"<setTimingAdvance(ms->msGetTA()); // This will set mtExpectedAckBSN if the message is sent. // If the MS gets this message, it will send Packet Control Acknowledgment // in the block specified by RRBP. if (os) { msg->text(*os); delete msg; return true; } else { GPRSLOG(1) << "GPRS sendReassignment "<downlink()-> send1MsgFrame(tbf,msg,2,MsgTransReassign,&tbf->mtReassignCounter); } } #endif static bool sendAssignmentPacch( PDCHL1FEC *pacch, // The PACCH channel. TBF *tbf, bool isNewAssignment, // If false, it is a reassignment. MsgTransactionType msgstate, std::ostream *os) // for testing - if set, print out the message instead of sending it. { MSInfo *ms = tbf->mtMS; // Send assignment message on PACCH. // A reassignment message is identical to an assignment message except // for the ControlAck bit, however, now we use timeslot reconfigure // for reassignments. RLCDownlinkMessage *msg; if (tbf->mtDir == RLCDir::Up) { RLCMsgPacketUplinkAssignment *ulmsg = new RLCMsgPacketUplinkAssignment(tbf); RLCMsgPacketUplinkAssignmentDynamicAllocationElt *dynelt; dynelt = ulmsg->setDynamicAllocation(); dynelt->setUplinkTFI(tbf->mtTFI); dynelt->setFrom(tbf,MultislotSymmetric); msg = ulmsg; } else { RLCMsgPacketDownlinkAssignment *dlmsg = new RLCMsgPacketDownlinkAssignment(tbf,isNewAssignment); msg = dlmsg; } // todo: stop timers for ms? // todo: start timers? msg->setTimingAdvance(ms->msGetTA()); msg->setTLLI(tbf->mtGetTlli()); // This will set mtExpectedAckBSN if the message is sent. // If the MS gets this message, it will send Packet Control Acknowledgment // in the block specified by RRBP. if (os) { msg->text(*os); delete msg; return true; } else { unsigned *pcounter = NULL; switch (msgstate) { case MsgTransAssign1: case MsgTransAssign2: GPRSLOG(1) << "GPRS sendAssignment "<mtAssignCounter; break; case MsgTransReassign: GPRSLOG(1) << "GPRS sendReassignment "<mtReassignCounter; break; default: devassert(0); } tbfDumpAll(); // This will increment the counter if the message is really sent. devassert(pcounter); return pacch->downlink()->send1MsgFrame(tbf,msg,2,msgstate,pcounter); } } // 2-14-2014: Create the L3ImmediateAssignment to assign a downlink TBF to this MS. // We create the L3ImmediateAssignment in the main gprs thread so we dont have to worry about locking anything, // then just before it is sent by the CCCH controller, gprsPageCcchSetTime is called to set the time. // This routine does not care whether the MS is in DRX mode or not; the caller tracks the drx timer // and sends the result on either PCH or generic CCCH depending on drx mode, but we dont yet track it very well. // GSM 44.60 5.5.1.5 Describes the conditions under which DRX should be applied. // The MS sends the DRX info to the SGSN in the Attach message. // None of that implemented yet. L3ImmediateAssignment *gprsPageCcchStart( PDCHL1FEC *pacch, // The PACCH channel. TBF *tbf) { assert(tbf->mtDir == RLCDir::Down); MSInfo *ms = tbf->mtMS; // The RequestReference is not used for this type of downlink assignment, // which contains TLLI instead; we have to set RequestReference to a number that // cannot possibly be confused with any valid value, ie, somewhere in the future, // at the time this is sent. RLCBSN_t impossibleBSN = gBSNNext + 10000; //RLCBSN_t impossibleBSN = resBSN + 1000; GSM::Time impossible(BSN2FrameNumber(impossibleBSN),0); // TN not used. // The downlink bit documentation is goofed up in GSM 4.08/4.18: They clarified it // in GSM 44.018 sec 10.5.2.25b: This bit is 1 only for downlink TBFs. L3ImmediateAssignment *result = new L3ImmediateAssignment( L3RequestReference(0,impossible), pacch->packetChannelDescription(), GSM::L3TimingAdvance(ms->msGetTA()), true,true); // for_tbf, downlink L3IAPacketAssignment *pa = result->packetAssign(); // DEBUG: I tried taking out power: // 12-16: Put power back in: pa->setPacketPowerOptions(GetPowerAlpha(),GetPowerGamma()); pa->setPacketDownlinkAssign(tbf->mtGetTlli(), tbf->mtTFI, tbf->mtChannelCoding(), tbf->mtUnAckMode, 1); //GPRSLOG(1) << "GPRS sendAssignment "<load()) << *result; LOGWATCHF("%s\n", tbf->tbfid(1)); tbfDumpAll(); tbf->mtAssignCounter++; tbf->mtCcchAssignCounter++; return result; } void sendAssignmentCcch( PDCHL1FEC *pacch, // The PACCH channel where the MS will be directed to send its answer. TBF *tbf, // The TBF that wants to initiate a downlink transfer to the MS. std::ostream *os) { MSInfo *ms = tbf->mtMS; string imsi = ms->sgsnFindImsiByHandle(ms->msGetHandle()); if (imsi.empty()) { LOG(ERR) << "Attempt to send TBF assignment on CCCH to MS without an IMSI?"; return; // This is a disaster. I dont know what is going to happen to this TBF; will it ever timeout? Just hope this never happens. } // Set an ack time far far in the future to keep the caller (mtServiceDownlink) from trying // to start another assignment. This ack time will be updated to the real value by gprsPageCcchSetTime(). tbf->mtSetAckExpected(gBSNNext + 1000,MsgTransAssign1); // We allocate a NewPagingEntry for convenience of queuing this request, but since this is not a real page the first argument (ChannelType) is unused. NewPagingEntry *npe = new NewPagingEntry(GSM::PSingleBlock1PhaseType,imsi); npe->mGprsClient = tbf; npe->mImmAssign = gprsPageCcchStart(pacch,tbf); // We dont support DRX yet, so just set the DRX time to the current frame, ie, assume all MS are in DRX if they are on CCCH. npe->mDrxBegin = gBTS.time().FN(); pagerAddCcchMessageForGprs(npe); } // WARNING: This is called from the CCCH thread. // Make the reservation and set the poll time in the L3ImmediateAssignment. // Return true on success or false on failure, in which case the caller should try again later. bool gprsPageCcchSetTime(TBF *tbf, L3ImmediateAssignment *iap, unsigned afterFrame) { LOG(DEBUG) <makeReservationInt(RLCBlockReservation::ForPoll, // We are requesting a confirming poll from the MS. afterBSN, // Reservation must be after this time. tbf, // Tells which tbf to inform when the poll arrives. Also used for statistics gathering. NULL, // RSSI, TimingAdvance not needed. NULL, // No RRBP MsgTransAssign1); // Dont inform anyone else. if (! resBSN.valid()) { // Abject failure. This should never happen because the reservation controller can make // reservations as far in advance as necessary. GPRSLOG(1) << "serviceRACH failed to make a reservation at" <mtSetAckExpected(resBSN,MsgTransAssign1); if (gFixIAUsePoll) { L3IAPacketAssignment *pa = iap->packetAssign(); pa->setPacketPollTime(resBSN.FN()); } return true; } // Return true if we send a block on the downlink. // NOTE: The L3ImmediateAssignment message can not assign multislot assignments. // NOTE: If MS T3204 (GSM04.08) 1 second, started when MS sends RACH, expires before // receiving ImmediateAssignment, MS aborts packet access procedure. bool sendAssignment( PDCHL1FEC *pacch, // The PACCH channel. TBF *tbf, std::ostream *os) // for testing - if set, print out the message instead of sending it. { MSInfo *ms = tbf->mtMS; // This did not help the cause=3105 errors. My idea was that the packet downlink assignment // is sent in the block immediately following the previous one, and I thought maybe there // Should we send the message on CCCH or PACCH? // It depends on which channel the MS is listening to now. // In short: if there are any active TBFs, or T3168 is running, or T3193 is running, // the MS is on PACCH. // The spec is all muddled about this; there is no clear state machine // showing what the MS is doing, rather, there are separate uplink and downlink // timers that get started in various modes, and it is not really even obvious // whether those timers should be started depending on other modes. bool onccch = (ms->getRROperatingMode() == RROperatingMode::PacketIdle); // We have maintained the T3193 and T3168 timers exclusively for this moment! // T3168 is for uplink requests initiated by the MS. // T3192/3193 are for a new network initiated downlink after completion of previous downlink. if (ms->msT3193.expired()) { ms->msT3193.reset(); } if (ms->msT3168.expired()) { ms->msT3168.reset(); if (tbf->mtDir == RLCDir::Up) { tbf->mtCancel(MSStopCause::T3168,TbfNoRetry); return false; } } if (ms->msT3193.active()) { onccch = false; } if (ms->msT3168.active()) { if (T3168Behavior) { // If T3168 is running, the MS supposedly ignores downlink assignments. // GSM04.60 7.1.3.1, and I quote: // "At sending of the PACKET RESOURCE REQUEST message, // the mobile station shall start timer T3168. Further more, // the mobile station shall not respond to PACKET DOWNLINK ASSIGNMENT messages − // but may acknowledge such messages if they contain a valid RRBP field − // while timer T3168 is running." GSM44.60 says something different. if (tbf->mtDir == RLCDir::Down) { return false; } // wait until later. } onccch = false; } // This is a total hack. After several attempts, just ignore // whether we think it should be on ccch or not, and alternate back and forth. // This only works for downlink assignments, which include TLLI. // The uplink immediate assignment on CCCH is only for one-phase access // after a RACH, and we dont use that. tbf->mtAssignmentOnCCCH = false; // For uplink TBF, always false. if (tbf->mtDir == RLCDir::Down) { if (tbf->mtAssignCounter < 4) { tbf->mtAssignmentOnCCCH = onccch; } else { tbf->mtAssignmentOnCCCH = ! tbf->mtAssignmentOnCCCH; } } if (GPRSDebug & 1) { GPRSLOG(1) <send1MsgFrame(tbf,msg,2,MsgTransTA,NULL); } bool MSInfo::msCanUseDownlinkTn(unsigned tn) { PDCHL1Downlink *down; RN_FOR_ALL(PDCHL1DownlinkList_t,msPCHDowns,down) { if (down->TN() == tn) {return true;} } return false; } bool MSInfo::msCanUseUplinkTn(unsigned tn) { PDCHL1Uplink *up; RN_FOR_ALL(PDCHL1UplinkList_t,msPCHUps,up) { if (up->TN() == tn) {return true;} } return false; } bool TBF::mtAllocateUSF() { // TODO WAY LATER: Is there a deadlock condition possible if multiple multislot MS // are in contention over USFs in multiple channels? // I dont think so because we run in one thread and so they cannot become codependent. PDCHL1Uplink *up; RN_FOR_ALL(PDCHL1UplinkList_t,mtMS->msPCHUps,up) { // Is this channel bidirectional? Otherwise the MS cannot use the USF. if (!mtMS->msCanUseDownlinkTn(up->TN())) {continue;} int usf = up->mchParent->allocateUSF(mtMS); // This only allocates a new USF if one is not already allocated for this MS. if (usf < 0) { // Failure. But we will keep the TFIs and USFs we have so far and try again later. GLOG(INFO) << "USF congestion on uplink"; return false; } mtMS->msUSFs[up->TN()] = usf; // It might already be assigned, or maybe new. } return true; } // Set the TFI in the channels in use by the MS that will be used for this TBF transaction. // The newvalue must be == tbf to set it, or == NULL to reset it. static void propagateTFI(TBF*tbf,int tfi,TBF*newvalue) { if (tbf->mtDir == RLCDir::Up) { PDCHL1Uplink *up; RN_FOR_ALL(PDCHL1UplinkList_t,tbf->mtMS->msPCHUps,up) { up->setTFITBF(tfi,RLCDir::Up,newvalue); } } else { PDCHL1Downlink *down; RN_FOR_ALL(PDCHL1DownlinkList_t,tbf->mtMS->msPCHDowns,down) { down->setTFITBF(tfi,RLCDir::Down,newvalue); } } } void TBF::mtFreeTFI() { if (mtTFI >= 0) { propagateTFI(this,mtTFI,NULL); // Reset tfi in all channels mtTFI = -1; } } bool TBF::mtAllocateTFI() { if (mtTFI < 0) { devassert(mtMS->msPCHDowns.size() && mtMS->msPCHUps.size()); PDCHL1FEC* pdch = mtMS->msPCHDowns.front()->parent(); int tfi = pdch->mchTFIs->findFreeTFI(mtDir); if (tfi < 0) { GLOG(INFO) << "TFI congestion on "<msAssignChannels()) return false; if (! mtAllocateTFI()) return false; if (mtDir == RLCDir::Up && ! mtAllocateUSF()) return false; mtAttached = true; mtSetState(TBFState::DataWaiting1); return true; } void TBF::mtSetState(TBFState::type wstate) { if (mtState != wstate) { //mtMsgReset(); // May be starting a new transaction message state. mtState = wstate; tbfDumpAll(); if (mtState == TBFState::Dead) { // TODO: We may be able to reduce the dead time by the length of time // the MS has not responded and we have not sent it any messages. // This is how long the TBF will be dead, ie, reserving its resources. int timerval = mtDir == RLCDir::Up ? gL2MAC.macT3169Value : gL2MAC.macT3195Value; mtDeadTime.setFuture(timerval); } if (mtState == TBFState::DataTransmit) { LOGWATCHF("%s Start%s bytes=%d down/up=%d/%d\n", tbfid(1), mtAssignmentOnCCCH ? " CCCH" : "", engineGetBytesPending(), mtMS->msPCHDowns.size(),mtMS->msPCHUps.size()); // FIXME: // There is some housekeeping to do. // If a previous uplink TBF died for this MS, and then the MS was issued // a new uplink channel assignment, it may still have the the old USFs reserved // for the old channel assignment. // We should free those now, but it is just an efficiency issue. } else if (mtState == TBFState::Finished) { LOGWATCHF("%s Fin\n", tbfid(1)); } else if (mtState == TBFState::Dead) { LOGWATCHF("%s Dead\n", tbfid(1)); } } } // Stop all the active TBFs associated with this MS in the specified dir. // They enter the dead state, which means their resources cannot be reused // until the timeout expires. void MSInfo::msStop(RLCDir::type dir, MSStopCause::type cause, TbfCancelMode cmode, unsigned howlong) // howlong in msecs to disable MS - unused now. { TBF *tbf; RN_MS_FOR_ALL_TBF(this,tbf) { if (dir != RLCDir::Either && tbf->mtDir != dir) continue; // 6-26-2012: Changing this test from isTransmitting to isActive. //if (tbf->isTransmitting()) if (tbf->isActive()) { tbf->mtCancel(cause,cmode); } } #if 0 //if (!msTxxxx.active()) { // GPRSLOG(1) << this <<" STOPPED" <mtGetState() == TBFState::Dead) { tbf->mtCancel(); } } msTxxxx.reset(); msT3191.reset(); msT3193.reset(); // Should have expired already, but be sure. //msMode = RROperatingMode::PacketIdle; } #endif // Writes a block of data to the MS static RLCDownEngine *createDownlinkTbf(MSInfo *ms, DownlinkQPdu *dlmsg, bool isRetry, ChannelCodingType codingMax) { ms->msStalled = 0; GPRSLOG(2) << "<---- downlink PDU"; RLCDownEngine *engine = new RLCDownEngine(ms); // Wrong! Removed 4-24 : ms->msT3193.reset(); // At this point the RLCEngine takes charge of the dlmsg memory. TBF *tbf = engine->getTBF(); tbf->mtTlli = dlmsg->mTlli; // Don't think mtTlli is used in a downlink TBF. tbf->mtChannelCodingMax = codingMax; //tbf->mtIsRetry = isRetry; engine->engineWriteHighSide(dlmsg); engine->mtSetState(TBFState::DataReadyToConnect); return engine; } // Service this MS, called from the service loop every RLCBSN time. // Counters and Timers defined in GSM04.60 sec 13. // Goes through msDownlinkQueue messages void MSInfo::msService() { // After the last downlink TBF, the MS waits until this timer expires // before going to PacketIdle mode. if (msT3193.expired()) { msT3193.reset(); // If there are no uplink TBFs going, the MS has dropped to PacketIdle mode. //if (! msCountActiveTBF(RLCDir::Up)) { // msMode = RROperatingMode::PacketIdle; //} } if (msIsSuspended()) {return;} if (msTBFs.size()) { msIdleCounter = 0; // Not idle } else { // List empty if (++msIdleCounter > gL2MAC.macMSIdleMax) { // default 600 seconds * blocks per second // LOG(DEBUG) << "Exceeded macMSIdleMax: " << gL2MAC.macMSIdleMax; msDelete(); // SVGDBG This removes an MS from gL2MAC return; } } // If MS running, check for counter expiration and stop if necessary. // =========== From GSM04.60 sec 13: ========== // N3101: // When the network after setting USF, receives a valid data block from the mobile station, it will // reset counter N3101. The network will increment counter N3101 for each USF for which no data // is received. N3101max shall be greater than 8. // N3103: // N3103 is reset when transmitting the final PACKET UPLINK ACK/NACK message within a TBF // (final ack indicator set to 1). If the network does not receive the PACKET CONTROL // ACKNOWLEDGEMENT message in the scheduled block, it shall increment counter N3103 and // retransmit the PACKET UPLINK ACK/NACK message. If counter N3103 exceeds its limit, the // network shall start timer T3169. // N3105: // When the network after sending a RRBP field in the downlink RLC data block , receives a valid // RLC/MAC control message from the mobile station, it will reset counter N3105. The network will // increment counter N3105 for each allocated data block for which no RLC/MAC control message // is received. The value of N3105max is network dependent. // =========================================== // (pat) Having multiple timers here is overkill in the spec; // they all do the same thing and will probably have the same value: // they wait to make sure the MS is in PacketIdle mode before releasing resources. // When N3101 or N3103 counter expires, timeout using T3169. // 7-5: I want to count RLC block periods, not USFs, so multiply by the number // of uplink channels in use. if (msN3101 * min(1,(int)msPCHUps.size()) > gL2MAC.macN3101Max) { msStop(RLCDir::Up,MSStopCause::N3101,TbfRetryAfterWait,gL2MAC.macT3169Value); } // TODO: //if (msN3103 > gL2MAC.macN3103Max) { // msStop(MSStopCause::N3103,gL2MAC.macT3169Value); //} // 12-22: I am taking this timer out for now, because it needs to be in the TBF, // not the MS. We will detect timeout here using RRBPs. If you want to put it back in, // move it to class TBF. //if (msT3191.expired()) { // Spec says when T3191 expires (5 secs) can release resources, // but we will wait another second. // msStop(MSStopCause::T3191,1000); //} // If MS is stopped, check for timer expiry and restart if necessary. //if (msTxxxx.expired()) { msRestart(); } // If there is a downlink message and this MS does not have any downlink TBFs running, // create a new TBF. //LOG(DEBUG) << "msDownlinkAttempts: " << msDownlinkAttempts; SVGDBG while (msDownlinkQueue.size()) { // We have something to send //LOG(DEBUG) << "Processing msDownlinkQueue message attempt: " << msDownlinkAttempts; SVGDBG // We will not start a new downlink TBF as long as there is any kind // of downlink TBF. // Formerly, (if 0==StallOnlyForActiveTBF) we also stalled for dead TBFS // (indicating the MS is probably unreachable) but that tended to kill off // active TBFs when any one died for mysterious reasons, so I turned it off. // 6-24-2012 UPDATE: I am going to reset StallOnlyForActive because we don't // have bugs and we now use dead tbfs to legitimately block downlinks until expiry. bool stallOnlyForActiveTBF = configGetNumQ("GPRS.TBF.StallOnlyForActive",0); TBF *blockingtbf; // Make sure there is something to process if (! msCountTBF2(RLCDir::Down,stallOnlyForActiveTBF?TbfMActive:TbfMAny,&blockingtbf)) { // Look in list of TBF's // (pat 3-2014) The 3 is just made up; allows the MS to ride out a simple bad connection. // The MS may also be non-responsive due to temporary suspension, which is not supported yet. if (msDownlinkAttempts >= 3) { // We already tried this and failed. msStop(RLCDir::Either,MSStopCause::NonResponsive,TbfNoRetry,gL2MAC.macT3169Value); msDelete(); // LOG(DEBUG) << "Greater than 3 attempts sending TBF count: " << msDownlinkAttempts; SVGDBG // SVGDBG should msDownlinkAttempts be reset here return; } // Send downlink message DownlinkQPdu *dlmsg = msDownlinkQueue.read(); // Gets entry from top of the queue // Because the message is queued for this MS, it means the tlli // is equal to either msTlli or msOldTlli. The SGSN tells us which // one to use. Make sure it is the current one. // The tlli is changed on the next message after an attach procedure. msChangeTlli(dlmsg->mTlli); createDownlinkTbf(this,dlmsg,false,ChannelCodingMax); // Write a block in msService msDownlinkAttempts++; } else { // This code just prints a nice message: devassert(blockingtbf); // SVGDBG fix if this is a crash // stalltype is 1 for stalled by active, 2 for stalled by dead tbf. unsigned stalltype = blockingtbf->isActive() ? 1 : 2; if (stalltype != msStalled) { GPRSLOG(2) << this <<" msDownlinkQueue stalled by " <<(stalltype==1 ? "active:" : "dead:") << blockingtbf; msStalled = stalltype; } } break; // SVGDBG msDownlinkAttempts may not work because msDownlinkAttempts can get reset in macServiceLoop } // If the MS has a delayed request an uplink TBF, start it up. // TODO: If the Q is small, try flow control? // FIXME: no longer necessary? //if (msUplinkRequest) { // TBF *tbf = TBF::newUpTBF(ms,msrmsg->mCRD); //if (ms->msCountActiveTBF(RLCDir::Up, &activeTbf) >= 1) //} // Over-riding TBF killer. // In the spec there is no such timer; instead there are timers for individual // states from the spec, but that does not include all the individual substates // we may go through, like reassignment. If any of those has a bug the TBF // state machine may hang forever. This would usually prevent that. // I am defaulting this timer to 6 secs which is longer than any other. if (msTBFs.size()) { // TODO: Should be TBF.NonResponsivve. int timerVal = gConfig.getNum("GPRS.Timers.MS.NonResponsive"); // value of 0 disables. if (timerVal > 0 && msTalkUpTime.elapsed() > timerVal) { // LOG(DEBUG) << "GPRS.Timers.MS.NonResponsive exceeded"; // SVGDBG msStop(RLCDir::Either,MSStopCause::NonResponsive,TbfNoRetry,gL2MAC.macT3169Value); } } if (((int)gBSNNext % 24) == 0) msTrafficMetric = msTrafficMetric / 2; } // The TBF ends either in mtFinishSuccess or mtCancel. void TBF::mtFinishSuccess() { // LOG(DEBUG) << "mtFinishSuccess"; // SVGDBG mtMS->msT3191.reset(); //GPRSLOG(1) << "@@@ok" << this <<" dir="<tbfDump(false)<msAddConnectTime(now.delta(mtStartTime)); // This includes the time it took the TBF to get its assignment through. // When a downlink TBF stops, set T3193, which measures how long the MS camps on the line. // Note that this timer runs even if there are intervening uplink TBFs. if (mtDir == RLCDir::Down) { mtMS->msT3193.set(); } else { // If the last uplink TBF ends and the T3193 is not running, // MS immediately enters PacketIdle mode. //int anyactive = msCountActiveTBF(RLCDir::Either); //if (!anyactive) { // mtMS->msMode = RROperatingMode::PacketIdle; //} } } // If the TBF never even started, the previous dlmsg will not have been pulled // off of the queue yet. void TBF::mtRetry() { // LOG(DEBUG) << "mtRetry"; //SVGDBG int retrycoding; bool retry = (mtDir == RLCDir::Down) && (retrycoding = configTbfRetry()); // in mtRetry if (mtMS->msDeprecated) { // No retry for MSInfo that has been replaced by some other TLLI. // We check again because deprecated may have changed between the time this TBF // was first attempted and when we get here. retry = false; } if (retry) { // Retry the last packet with a possibly slower codec: ChannelCodingType chCoding = (ChannelCodingType) RN_BOUND((retrycoding-1),ChannelCodingCS1,ChannelCodingCS4); RLCDownEngine *oldengine = getDownEngine(); // TODO: We would like to retry all the PDUs that were not acknowledged, // but right now we only keep the last. // The mDownlinkPdu might be NULL if the engine never started, ie, MS didnt get the assignment. DownlinkQPdu *dlmsg = NULL; if (oldengine->mDownlinkPdu) { //dlmsg = new DownlinkQPdu(*oldengine->mDownlinkPdu); // retrychannel=1 implies ChannelCodingCS1 dlmsg = oldengine->mDownlinkPdu; oldengine->mDownlinkPdu = NULL; } else { dlmsg = mtMS->msDownlinkQueue.readNoBlock(); // Aka pop_front } if (dlmsg) { // Not possible to be NULL, but be safe. if (dlmsg->mDlTime.elapsed() < gConfig.getNum("GPRS.TBF.Expire")) { createDownlinkTbf(mtMS, dlmsg, true, chCoding); // In mtRetry } else { // Too old. Give up. // LOG(DEBUG) << "Exceeded GPRS.TBF.Expire time"; //SVGDBG delete dlmsg; } } // The old tbf and engine will be deleted momentarily. } mtSetState(TBFState::Dead); } // Kill the TBF with prejudice, either because it timed out, // or for reasons beyond its purview, like we are shutting down GPRS. // Same actions in either case. void TBF::mtCancel(MSStopCause::type cause, TbfCancelMode release) // When to release and whether to retry. { // LOG(DEBUG) << "mtCancel mtState: " << mtState; //SVGDBG // Clear out any reservation, in case the reservation does try to notify // this tbf, which will no longer exist. This is probably overkill, // because either the reservations were answered and cleaned up and they // weren't, in which case this turned into a dead tbf, // and we keep dead tbfs around for 5 seconds, and their reservations // should be long passed over. // NO DONT DO THIS!!! The thing may actually respond at that time. // PDCHL1Downlink *down; //if (mtExpectedAckBSN.valid()) { // RN_FOR_ALL(PDCHL1DownlinkList_t,mtMS->msPCHDowns,down) { // down->parent()->clearReservation(mtExpectedAckBSN,this); // } //} // Keep separate statistics for TBF that never got a connection. switch (mtState) { default: mtMS->msCountTbfNoConnect++; break; case TBFState::DataTransmit: case TBFState::DataFinal: case TBFState::Finished: { Timeval now; mtMS->msAddConnectTime(now.delta(mtStartTime)); // This includes the time it took the TBF to get its assignment through. mtMS->msCountTbfFail++; break; } } if (mtDir == RLCDir::Up) { mtMS->msFailUSFs(); // Cant use these USFs for a different MS for 5 seconds. } if (mtMS->msDeprecated) { release = TbfNoRetry; } std::string ss = tbfDump(true); bool retry = false, needRelease = false; const char *what = ""; switch (release) { case TbfRetryInapplicable: case TbfNoRetry: what = "@@@failed tbf"; break; case TbfRetryAfterRelease: needRelease = (mtDir == RLCDir::Down) && isTransmitting(); what = "@@@release tbf"; goto retryafterwait; case TbfRetryAfterWait: what = "@@@failed tbf"; retryafterwait: retry = (mtDir == RLCDir::Down) && ! mtMS->msDeprecated && configTbfRetry(); break; } GLOG(NOTICE) << timestr() << what <msT3191.reset(); // If it is a shutdown cause or an error during starting up the TBF, we kill it immediately. // If the TBF was in a transmit mode, we need to send a PacketTBFRelease message. // I think we are supposed to be able to set mControlAck in the PacketDownlinkAssignment // but the Blackberry does not implement that properly, probably because the documentation is unclear. // Update: other phones do implement it properly, ie, if the controlack bit is set, // the assignment creates a new TBF. // We only send the TBFRelease message in transmit mode, even though in DataWaiting modes the TBF is // already started, because the reason we are sending it at all is so that a new TBF does // not try to use RLC acknowledged mode to retrieve blocks from the previous (released) tbf. // So it only matters if we have started transmitting blocks. // Update: I am just going to send this in all transmitting modes, because we should // do it for DataReassign or DataFinal also, and we rarely even use the DataWaiting2 mode. // If we were in state TbfRelease then we set kill time in the previous call // to mtCancel and we dont need to add any additional time to mtKillTime. // That is the only way mtDeadTime can be valid on entry to this function. //if (mtGetState() != TBFState::TbfRelease) { //mtKillTime = gBSNNext.addTime(gL2MAC.macT3169Value); //} mtCause = cause; mtSetState(needRelease ? TBFState::TbfRelease : TBFState::Dead); if (retry && release == TbfRetryAfterWait) { mtRetry(); } } // Handle the few cases for a TBF that does not have channels // assigned to its MS yet. void TBF::mtServiceUnattached() { LOG(INFO) << "mtServiceUnattached mtState: " << mtState; // SVGDBG Make DEBUG // Dead tbfs are attached, so dont test this flag. //if (mtAttached) return; switch (mtState) { case TBFState::Unused: // This state may occur legally during testing. GLOG(ERR) << "GPRS found TBF with uninitialized state\n"; // Fix it so we wont see this message again. mtCancel(MSStopCause::CauseUnknown, TbfNoRetry); //mtState = TBFState::Dead; LOG(INFO) << "mtServiceUnattached Unused"; // SVGDBG Make DEBUG return; case TBFState::DataReadyToConnect: if (mtAttach()) { mtSetState(TBFState::DataWaiting1); } LOG(INFO) << "mtServiceUnattached DataReadyToConnect"; // SVGDBG Make DEBUG return; case TBFState::Deleting: casedeleting: if (mtMsgPending()) { // Wait until the response must have been received, // to make sure it gets delivered to the right TBF. // This is overkill - whoever put this in Deleting state already did this. LOG(INFO) << "mtServiceUnattached Deleting mtMsgPending"; // SVGDBG Make DEBUG return; } mtDelete(); // Cleans up and deletes. LOG(INFO) << "mtServiceUnattached Deleting mtDelete"; // SVGDBG Make DEBUG return; case TBFState::Dead: // A dead TBF is normally still "attached", ie, hanging onto its resources // to prevent someone else from using them, until its killtime expires. // But the MS may lose its channel assignment (eg, due to RACH) // so this case may not be handled by the attached tbf code. LOG(INFO) << "mtServiceUnattached Dead"; // SVGDBG Make DEBUG devassert(mtDeadTime.valid()); if (mtDeadTime.expired()) { mtDetach(); LOG(INFO) << "mtServiceUnattached expired"; // SVGDBG Make DEBUG goto casedeleting; } return; default: LOG(INFO) << "mtServiceUnattached default"; // SVGDBG Make DEBUG return; } } // Generic check for non-reponsive ms before sending another message. // If we have sent the same message more than a specified number of times, // consider the TBF dead. // There is no specific mention of some of these timeout conditions in the spec, // so just use reasonable values. // Previously I stopped the whole MS for these cases, but sometimes the MS // simply refuses to respond to one particular TBF, so instead, just kill the // specific TBF that is non-responsive. //bool TBF::mtNonResponsive() //{ // if (mtSendTries > gConfig.getNum("GPRS.Counters.Misc",10)) { // mtCancel(MSStopCause::Misc); // return true; // } // if (mtN3105 > gL2MAC.macN3105Max) { // mtCancel(MSStopCause::N3105); // return true; // } // return false; //} bool TBF::isPrimary(PDCHL1Downlink *down) { return (down->parent() == mtMS->msPacch); } bool TBF::wantsMultislot() { if (mtDir == RLCDir::Down) { return mtMS->msPCHDowns.size() >= 2; } else { return mtMS->msPCHUps.size() >= 2; } } // If we get a response to TbfRelease, try to restart the TBF. // Return true if we sent something on the downlink. // We depend on setState resetting the msgAck flag. bool TBF::mtSendTbfRelease(PDCHL1Downlink *down) { LOG(INFO) << "mtSendTbfRelease"; // SVGDBG Make DEBUG if (mtMsgPending()) { return false; } // Wait for message in progress. if (mtGotAck(MsgTransTbfRelease,true)) { mtDetach(); //mtSetState(TBFState::Dead); // redundant, done inside mtRetry() mtRetry(); return false; } if ((int)mtTbfReleaseCounter > gConfig.getNum("GPRS.Counters.TbfRelease")) { LOG(INFO) << "GPRS.Counters.TbfRelease count exceeded"; // SVGDBG Make DEBUG mtCancel(MSStopCause::ReleaseCounter,TbfRetryAfterWait); return false; } RLCMsgPacketTBFRelease *rmsg = new RLCMsgPacketTBFRelease(this); return down->send1MsgFrame(this,rmsg,2,MsgTransTbfRelease,&mtTbfReleaseCounter); } // See if the TBF can send anything on this downlink, and return true if it sent a block. bool TBF::mtServiceDownlink(PDCHL1Downlink *down) { LOG(INFO) << "mtServiceDownlink mtState: " << mtState; // SVGDBG Make DEBUG // Only send messages on PACCH. while (1) { mac_debug(); switch (mtState) { case TBFState::Unused: // This state may occur legally during testing. GLOG(ERR) << "GPRS found TBF with uninialized state\n"; mtCancel(MSStopCause::CauseUnknown, TbfNoRetry); //mtState = TBFState::Dead; // Fix it so we wont see this message again. return false; case TBFState::DataReadyToConnect: if (mtAttach()) { mtSetState(TBFState::DataWaiting1); continue; } return false; case TBFState::DataWaiting1: // Waiting for ACK to assignment if (! isPrimary(down)) { return false; } // A non-responsive MS is detected by too many mtAssignCounter. if (mtGotAck(MsgTransAssign1,true)) { if (mtDir == RLCDir::Up) { // If the MS Rached us then this timer is running; // must reset it when the MS receives the assignment. mtMS->msT3168.reset(); } mtSetState(TBFState::DataWaiting2); continue; } else if (! mtMsgPending()) { // DEBUG: Try sending extra TA messages. //if (mtAssignCounter > 6 && !mtTASent && sendTA(down,this)) { mtTASent=1; return true; } if ((int)mtAssignCounter > gConfig.getNum("GPRS.Counters.Assign")) { mtCancel(MSStopCause::AssignCounter,TbfNoRetry); return false; } if (mtAssignmentOnCCCH && ! gFixIAUsePoll) { // We will never get a response since we didnt poll. // The second time through this loop, just go to the next state. // The ExpectedAckBSN is valid even though we are not polling because we are // using it basically as a timer to wait until the message is sent on AGCH. //if (mtExpectedAckBSN.valid()) if (mtMsgPending()) { mtSetState(TBFState::DataWaiting2); continue; } } bool result = sendAssignment(down->parent(),this,NULL); // else we wait in this state for the poll result return result; } return false; case TBFState::DataWaiting2: if (! isPrimary(down)) { return false; } if (mtAssignmentOnCCCH) { if (mtDir == RLCDir::Down) { // See GSM4.60 7.2.2.1 // We do not have to send a timing advance message // because we included a starting time in the // assignment message, but it is useful for debugging // to see if the MS responds. if (SendExtraTA && ! sendTA(down,this)) { return false; } mtSetState(TBFState::DataTransmit); return true; // We sent a message. } } mtSetState(TBFState::DataTransmit); continue; case TBFState::DataReassign: devassert(0); #if 0 // Wait for existing messages to clear. if (mtMsgPending()) {return false;} // Did we get the ack to the reassignment? if (mtGotAck(MsgTransReassign,true)) { mtSetState(TBFState::DataTransmit); continue; } if ((int)mtReassignCounter > gConfig.getNum("GPRS.Counters.Reassign")) { mtCancel(MSStopCause::ReassignCounter,TbfRetryAfterWait); return false; } // Send the reassignment. // This is currently used only in the case where an uplink TBF // wants to change its priority. devassert(tbf->mtDir == RLCDir::Up); if (sendTimeslotReconfigure(down->parent(),this,NULL)) { return true; } // TODO: While we are waiting for the above, we could be sending // blocks or setting USFs on the old channels, but for now // we will not send any more blocks until the reassign occurs. return false; // We did not use the downlink. #endif case TBFState::DataTransmit: if (mtAssignmentOnCCCH) { if (mtGotAck(MsgTransAssign2,true)) { // Woo hoo! We are multislot now. mtAssignmentOnCCCH = false; // Turn off the reassign too, since we just did an additional reassign. mtPerformReassign = false; // And fall through to service the TBF on this channel. } else { // If the assignment was sent on CCCH we can not set up multislot, // so we will use PACCH exclusively for the nonce. if (! isPrimary(down)) { return false; } if (wantsMultislot()) { // We would like to be multislot, but we are not yet. // A multislot assignment can not be included in // the L3ImmediateAssignment message on CCCH. // So to do multislot we will need to send two messages // the initial assignment and then another // on PACCH to get into multislot mode. if (! mtMsgPending()) { // Send a second assignment to set up multislot. if (sendAssignmentPacch(down->parent(),this,false,MsgTransAssign2,NULL)) { return true; } } } } } #if OLD_REASSIGN if (mtPerformReassign) { // We are sending a reassign because the MS requested a new priority for this TBF. if (mtGotAck(MsgTransReassign,true)) { mtPerformReassign = false; // And fall through to service the TBF on this channel. } else { if (! mtMsgPending()) { if ((int)mtReassignCounter > gConfig.getNum("GPRS.Counters.Reassign")) { // The iphone is not answering these. It may be because we are only allowed // to have 3 RRBPs out at a time, but whatever, dont kill the TBF for this, // just stop sending the messages. If the MS wants //mtCancel(MSStopCause::ReassignCounter,TbfRetryAfterWait); // return false; mtPerformReassign = false; } else { if (sendAssignmentPacch(down->parent(),this,false,MsgTransReassign,NULL)) { return true; } } } // Otherwise fall through to utilize the channel normally. } } #endif // Nonresponsive uplink is detected by N3101 (too many unanswered USF) // in the msService routine, or N3103 in DataFinal mode. // Nonresponsive downlink is detected by N3105 (no answer to RRBP data block), // or when finished by T3191 expiry with no downlinkacknack received from MS. // When this overflows the final reservation is still outstanding. // TODO: fix this minor problem. if (mtN3105 > gL2MAC.macN3105Max) { if (mtAssignmentOnCCCH && !gFixIAUsePoll) { // This error indicates the assignment failed. // Go back and try it again. mtSetState(TBFState::DataWaiting1); continue; } mtCancel(MSStopCause::N3105,TbfRetryAfterRelease); return false; } return engineService(down); case TBFState::DataFinal: if (mtAssignmentOnCCCH && ! isPrimary(down)) { return false; } // Nonresponsive uplink detected by N3103 (no answer to RRBP in final acknack). // Not applicable to downlink. if (mtN3103 > gL2MAC.macN3103Max) { mtCancel(MSStopCause::N3103,TbfRetryInapplicable); return false; } // For a downlink, we have to wait for the acknack from the MS. // For an uplink, we have to wait for the RRBP response // to the acknack that we sent the MS. // The engineService routine takes care of this. return engineService(down); //case TBFState::TbfRelease1: // // Wait for any existing reservations to clear first. // if (gBSNNext >= mtKillTime) { mtSetState(TBFState::Dead); continue; } // if (! mtMsgPending()) { mtSetState(TBFState::TbfRelease2); continue; } // return false; // Currently TbfRelease is used only when killing a TBF. // mtSendTbfRelease will retry the TBF if possible. case TBFState::TbfRelease: if (! isPrimary(down)) { return false; } // This extra check for killtime is no longer needed because // we check in the msService loop. //if (gBSNNext >= mtKillTime) { mtSetState(TBFState::Dead); continue; } // Send the TBF Release message. return mtSendTbfRelease(down); case TBFState::Finished: // Hang around in this state until we are sure // the MS has stopped talking to us. if (! mtMsgPending()) { mtDetach(); } return false; case TBFState::Dead: // A dead TBF is still attached, ie, holding onto USF and TFI // resources, but the MS may or may not have channel assignments, // so this case must appear both here and in mtServiceUnattached. devassert(mtDeadTime.valid()); if (mtDeadTime.expired()) { mtDetach(); } return false; case TBFState::Deleting: return false; } } } }; // namespace