Files
openbts/GPRS/TBF.cpp

1487 lines
55 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 <ControlTransfer.h>
#include <GSMCCCH.h>
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:" <<LOGVAR(mtExpectedAckBSN)
//<<LOGVAR(gBSNNext) <<" cmp=" << (mtExpectedAckBSN >= gBSNNext);
// Wait lag-time extra blocks beyond when we expect
// it to make sure we dont launch another sendassignment
// at the same time it is being received.
// Note that the operators here are in class RLCBSN_t and do modulo logic.
if (mtMsgExpectedBits) {
for (int i = 0; i < MsgTransMax; i++) {
if (mtExpectedAckBSN[i].valid() && mtExpectedAckBSN[i] + BSNLagTime >= gBSNNext) return true;
}
}
// We could probably clear mtMsgExpectedBits at this point...
return false;
}
TBF *TBF::newUpTBF(MSInfo *ms,RLCMsgChannelRequestDescriptionIE &mCRD,
uint32_t tlli,
bool onRach) // If true, request on RACH, else request on uplink ack-nack.
{
// Changed 5-20-2012. There is only ONE uplink tbf,
// and assigning a new one just changes the priority of the existing.
// Note that there is also a GPRS optional feature to allow multiple simultaneous
// separate TBFs with separate RLCs, but we dont support it.
TBF *activeTbf = 0;
ms->msN3101 = 0; // I'm going to reset this no matter what happens below.
if (ms->msCountActiveTBF(RLCDir::Up, &activeTbf) >= 1) {
//GLOG(INFO) << "MS denied additional uplink TBF because GPRS.TBF.Up.Max limit exceeded";
if (activeTbf->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"<<LOGVAR(tlli)<<"does not match MS:"<<ms;
// But go ahead and do it anyway.
}
GPRSLOG(1) << "Changing uplink TBF tlli to"<<LOGVAR(tlli)<<ms;
activeTbf->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"<<ms;
}
tbf->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["<<i<<"]="<<mtExpectedAckBSN[i];
}
}
}
std::string TBF::tbfDump(bool verbose) const
{
std::ostringstream os;
//int tn = -1, usf = 0;
//PDCHL1FEC *pacch = mtMS->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="<<engineDownPDUSize(); }
if (!verbose) return os.str();
os << "\n\t";
MsgTransaction::text(os);
os << "\n";
os << "\t";
//os << LOGVARRANGE("ChannelCoding",mtChannelCoding(),mtCCMin,mtCCMax); //moved to SignalQuality.
os << LOGVAR(mtUnAckMode);
os <<LOGVAR2("OnCCCH",mtAssignmentOnCCCH);
if (mtAssignCounter) os << LOGVAR(mtAssignCounter);
if (mtReassignCounter) os << LOGVAR(mtReassignCounter);
if (mtPerformReassign) os << LOGVAR(mtPerformReassign);
if (mtMS->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<<LOGVAR2("msT3193",mtMS->msT3193.remaining()); }
if (mtMS->msT3168.active()) { os<<LOGVAR2("msT3168",mtMS->msT3168.remaining()); }
if (mtDescription.size()) os <<" descr="<<mtDescription;
os <<"\n";
mtMS->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<RLCDownEngine *>(this) : NULL;
}
//RLCDownEngine const *TBF::getDownEngine() const
//{
// return (mtDir == RLCDir::Down) ? dynamic_cast<RLCDownEngine const*>(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:"<<LOGHEX(tlli);
}
}
return tlli;
}
#if 0
// 7-25: Neither the Blackberry nor Multitech like this message.
// Maybe because I include the downlink assignment when there is no downlink TBF?
static bool sendTimeslotReconfigure(
PDCHL1FEC *pacch, // The PACCH channel.
TBF *tbf, // This is an uplink tbf.
std::ostream *os) // for testing - if set, print out the message instead of sending it.
{
RLCMsgPacketTimeslotReconfigure *msg = new RLCMsgPacketTimeslotReconfigure(tbf);
//msg->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 "<<tbf<<" sending:" << msg;
tbfDumpAll();
// This will increment the counter if the message is really sent.
return pacch->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 "<<tbf<<" sending:" << msg;
pcounter = &tbf->mtAssignCounter;
break;
case MsgTransReassign:
GPRSLOG(1) << "GPRS sendReassignment "<<tbf<<" sending:" << msg;
pcounter = &tbf->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 "<<tbf<<" sending L3ImmediateAssignment:"
// <<LOGVAR2("agchload",currentAGCH->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) <<LOGVAR(iap)<<LOGVAR(afterFrame)<<LOGVAR(tbf);
// Make a reservation for the poll response.
RLCBSN_t afterBSN = FrameNumber2BSN(afterFrame);
PDCHL1FEC *chan = gL2MAC.macPickChannel(); // pick the least busy channel;
RLCBSN_t resBSN = chan->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" <<LOGVAR(gBSNNext);
// The MS may try another RACH for us again later,
// or give up and try some other cell that it can also hear.
return false;
}
// We have to wait for the time whether we sent the poll or not.
tbf->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) <<ms <<" OnCCCH="<<tbf->mtAssignmentOnCCCH
<<" RRMode="<<ms->getRROperatingMode()
<<" T3193="<<(ms->msT3193.active()?ms->msT3193.remaining():0)
<<" T3168="<<(ms->msT3168.active()?ms->msT3168.remaining():0);
}
if (tbf->mtAssignmentOnCCCH) {
// (pat) 2-2014: Changing the CCCH from push to pull, so we send a message to the CCCH code and
// it will call us back when the CCCH is available.
sendAssignmentCcch(pacch,tbf,os);
return false; // We did not use the packet channel downlink.
} else {
// Send assignment message on PACCH.
return sendAssignmentPacch(pacch,tbf,true,MsgTransAssign1,os);
}
}
#
static bool sendTA(PDCHL1Downlink *down,TBF *tbf)
{
RLCMsgPacketPowerControlTimingAdvance *tamsg = new RLCMsgPacketPowerControlTimingAdvance(tbf);
// DEBUG: If you change the TFI or downlink flag, this
// is unanswered, as expected:
//tamsg->setGlobalTFI(1,tbf->mtTFI);
GPRSLOG(1) << "GPRS sendTA "<<tbf<<" sending:" << tamsg;
RLCDownlinkMessage *msg = tamsg;
// Lets ask for a response and see what happens:
return down->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 "<<mtDir;
return false;
}
mtTFI = tfi;
}
propagateTFI(this,mtTFI,this); // Set TFI in all channels in use by MS.
return true;
}
bool TBF::mtAttach()
{
MSInfo *ms = mtMS;
// msAssignChannels only does something if this is the first transaction.
if (! ms->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" <<LOGHEX(msTLLI) <<" cause="<<(int)cause;
// msTxxxx.set(howlong);
// // DEBUG: Dont do it;
// return;
// msStopCause = cause;
// // Suspend all the tbfs.
// TBF *tbf;
// RN_FOR_ALL(TBFList_t,msTBFs,tbf) {
// if (tbf->isActive()) { tbf->mtSetState(TBFState::Dead); }
// }
// GLOG(INFO) << "MS with " <<LOGHEX(msTLLI) << " temporarily stopped, cause: " << (int)cause;
//}
#endif
}
#if 0
void MSInfo::msRestart()
{
TBF *tbf;
RN_MS_FOR_ALL_TBF(this,tbf) {
// We only delete dead tbfs, ie, the ones that we stopped previously.
// That is so if someone changes this code in the future to allow
// new TBFs to start during the 5 second delay in resource release,
// we wont delete those.
if (tbf->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="<<mtDir <<" descr="<<mtDescription <<" OnCCCH="<<mtAssignmentOnCCCH;
GPRSLOG(1) << "@@@ok" << this->tbfDump(false)<<timestr();
mtSetState(TBFState::Finished);
Timeval now;
mtMS->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 <<LOGVAR2("cause",cause) << ss;
if (GPRSDebug) {
std::cout << timestr() <<what <<LOGVAR2("cause",cause) << ss<<"\n";
}
mtMS->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