/* * Copyright 2011 Range Networks, Inc. * All Rights Reserved. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribuion. * * 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. */ #include "GPRSInternal.h" #include "TBF.h" #include "RLCEngine.h" #include "MAC.h" #include "FEC.h" #include "RLCMessages.h" #include "Interthread.h" #include "BSSG.h" #include "LLC.h" #define strmatch(what,pat) (0==strncmp(what,pat,strlen(pat))) using namespace BSSG; using namespace SGSN; #define BAD_NUM_ARGS 1 // See CLI/CLI.cpp #define RN_CMD_OPTION(opt) (argi1 && 0==strncmp(argv[1],o,strlen(o)) ? argc--,argv++,1 : 0) namespace GPRS { static int gprsMem(int argc, char **argv, int argi, ostream&os) { gMemStats.text(os); return 0; } static void printChans(bool verbose, ostream&os) { PDCHL1FEC *pch; RN_MAC_FOR_ALL_PDCH(pch) { pch->mchDump(os,verbose); } } //static int gprsChans(int argc, char **argv, int argi, ostream&os) //{ // bool verbose=0; // while (argi < argc) { // if (strmatch(argv[argi],"-v")) { verbose = 1; argi++; continue; } // os << "oops! unrecognized arg:" << argv[argi] << "\n"; // return 0; // } // printChans(verbose,os); // return 0; //} static int gprsList(int argc, char **argv, int argi, ostream&os) { bool xflag=0, aflag=0, listms=0, listtbf=0, listch=0; int options = 0; int id = -1; while (argi < argc) { if (strmatch(argv[argi],"ch")) { listch = 1; argi++; continue; } if (strmatch(argv[argi],"tbf")) { listtbf = 1; argi++; continue; } if (strmatch(argv[argi],"ms")) { listms = 1; argi++; continue; } if (strmatch(argv[argi],"-v")) { options |= printVerbose; argi++; continue; } if (strmatch(argv[argi],"-c")) { options |= printCaps; argi++; continue; } if (strmatch(argv[argi],"-x")) { xflag = 1; argi++; continue; } if (strmatch(argv[argi],"-a")) { aflag = 1; argi++; continue; } if (isdigit(argv[argi][0])) { if (id >= 0) goto oops; // already found a number id = atoi(argv[argi]); argi++; continue; } oops: os << "oops! unrecognized arg:" << argv[argi] << "\n"; return 0; } bool all = !(listch|listtbf|listms); if (all|listms) { MSInfo *ms; for (RListIterator itr(xflag ? gL2MAC.macExpiredMSs : gL2MAC.macMSs); itr.next(ms); ) { if (id>=0 && (int)ms->msDebugId != id) continue; if (aflag || ! ms->msDeprecated) { ms->msDump(os,(PrintOptions)options); } } } if (all|listtbf) { TBF *tbf; //RN_MAC_FOR_ALL_TBF(tbf) { os << tbf->tbfDump(verbose); } for (RListIterator itr(xflag ? gL2MAC.macExpiredTBFs : gL2MAC.macTBFs); itr.next(tbf); ) { // If the id matches a MS, print the TBFs associated with that MS. if (id>=0 && (int)tbf->mtDebugId != id && (int)tbf->mtMS->msDebugId != id) continue; os << tbf->tbfDump(options&printVerbose) << endl; } } if (all|listch) { printChans(options&printVerbose,os); } return 0; } static int gprsFree(int argc, char **argv, int argi, ostream&os) { char *what = RN_CMD_ARG; char *idstr = RN_CMD_ARG; if (!idstr) return BAD_NUM_ARGS; int id = atoi(idstr); MSInfo *ms; TBF *tbf; if (strmatch(what,"ms")) { RN_MAC_FOR_ALL_MS(ms) { if (ms->msDebugId == (unsigned)id) { os << "Deleting " <msDelete(1); return 0; } } os << "MS# "<mtDebugId == (unsigned)id) { os << "Deleting " <mtDelete(1); return 0; } } os << "TBF# "< itr(gL2MAC.macExpiredMSs); itr.next(ms); ) { itr.erase(); delete ms; } TBF *tbf; for (RListIterator itr(gL2MAC.macExpiredTBFs); itr.next(tbf); ) { itr.erase(); delete tbf; } return 0; } static int gprsStats(int argc, char **argv, int argi, ostream&os) { if (!GPRSConfig::IsEnabled()) { os << "GPRS is not enabled. See 'GPRS.Enable' option.\n"; return 0; } GSM::Time now = gBTS.time(); os << "GSM FN=" << now.FN() << " GPRS BSN=" << gBSNNext << "\n"; os << "Current number of" << " PDCH=" << gL2MAC.macPDCHs.size() << " MS=" << gL2MAC.macMSs.size() << " TBF=" << gL2MAC.macTBFs.size() << "\n"; os << "Total number of" << " PDCH=" << Stats.countPDCH << " MS=" << Stats.countMSInfo << " TBF=" << Stats.countTBF << " RACH=" << Stats.countRach << "\n"; os << "Downlink utilization=" << gL2MAC.macDownlinkUtilization << "\n"; os << LOGVAR2("ServiceLoopTime",Stats.macServiceLoopTime) << "\n"; return 0; } #if 0 // pinghttp test code not linked in yet. static int gprsPingHttp(int argc, char **argv, int argi, ostream&os) { if (argi >= argc) { os << "syntax: gprs pinghttp address\n"; return 1; } //char *addr = argv[argi++]; os << "pinghttp unimplemented\n"; return 0; } #endif // Start the service and allocate a channel. // This is redundant - can call rach. static int gprsStart(int argc, char **argv, int argi, ostream&os) { // Start the thread, if not running. char *modearg = RN_CMD_ARG; if (modearg) { gL2MAC.macSingleStepMode = strmatch(modearg,"s"); if (!gL2MAC.macSingleStepMode) { os << "Unrecognized arg: "<getTBF(); RLCUpEngine *upengine = new RLCUpEngine(ms,0); TBF *uptbf = upengine->getTBF(); uptbf->mtAttach(); // Assigns USF and TFI for the tbf, so we can see it in the messages. downtbf->mtAttach(); os << "uplink TBF for messages is:\n"; os << uptbf->tbfDump(1); os << "downlink TBF for messages is:\n"; os << downtbf->tbfDump(1); os << "struct RLCMsgPacketUplinkDummyControlBlock : public RLCUplinkMessage\n"; struct RLCMsgPacketUplinkDummyControlBlock msgupdummy(&rb); msgupdummy.text(os); os << "\n\nstruct RLCMsgPacketDownlinkAckNack : public RLCUplinkMessage\n"; RLCMsgPacketDownlinkAckNack msgdownacknack(&rb); msgdownacknack.text(os); os << "\n\nstruct RLCMsgPacketControlAcknowledgement : public RLCUplinkMessage\n"; RLCMsgPacketControlAcknowledgement msgcontrolack(&rb); msgcontrolack.text(os); os << "\n\nstruct RLCMsgPacketResourceRequest : public RLCUplinkMessage\n"; RLCMsgPacketResourceRequest resreq(&rb); resreq.text(os); os << "\n\nclass RLCMsgPacketAccessReject : public RLCDownlinkMessage\n"; RLCMsgPacketAccessReject msgreject(downtbf); msgreject.text(os); os << "\n\nclass RLCMsgPacketTBFRelease : public RLCDownlinkMessage\n"; RLCMsgPacketTBFRelease msgtbfrel(downtbf); msgtbfrel.text(os); os << "\n\nclass RLCMsgPacketUplinkAckNack : public RLCDownlinkMessage\n"; RLCMsgPacketUplinkAckNack *msgupacknack = upengine->engineUpAckNack(); msgupacknack->text(os); delete msgupacknack; os << "\n\nstruct RLCMsgPacketDownlinkDummyControlBlock : public RLCDownlinkMessage\n"; RLCMsgPacketDownlinkDummyControlBlock msgdowndummy; msgdowndummy.text(os); os << "\n\nL3ImmediateAssignment for Single Block Packet Assignment\n"; //ms->msMode = RROperatingMode::PacketIdle; sendAssignment(pdch,downtbf, &os); os << "\n\nclass RLCMsgPacketDownlinkAssignment : public RLCDownlinkMessage\n"; //ms->msMode = RROperatingMode::PacketTransfer; ms->msT3193.set(); // To force the MS to send the message on PACH we can set T3191 sendAssignment(pdch,downtbf, &os); ms->msT3193.reset(); os << "\n\nclass RLCMsgPacketUplinkAssignment : public RLCDownlinkMessage\n"; sendAssignment(pdch,uptbf, &os); return 0; } static int gprsTestBSN(int argc, char **argv, int argi, ostream&os) { RLCBSN_t bsn = 0; int fn = 0; for (fn = 0; fn < 100; fn++) { bsn = FrameNumber2BSN(fn); int fn2 = BSN2FrameNumber(bsn); os << LOGVAR(fn) < 0; numwords--) { mcw.writeField(dataword++,16); // Write as a 16 bit word. } // The BitVector now looks like something the MS would send us. // Go through the steps GPRS code uses to parse the incoming BitVector: // The GSM radio queues us an RLCRawBlock: RLCRawBlock *rawblock = new RLCRawBlock(bsn,vec,0,0,ChannelCodingCS1); return rawblock; } #if INTERNAL_SGSN==0 static int gprsTestUl(int argc, char **argv, int argi, ostream&os) { bool randomize = RN_CMD_OPTION("-r"); int32_t mytlli = 6789; MSInfo *ms = new MSInfo(mytlli); RLCUpEngine *upengine = new RLCUpEngine(ms,0); TBF *uptbf = upengine->getTBF(); InterthreadQueue testQ; gBSSG.mbsTestQ = &testQ; // Put uplink blocks on our own queue. int payloadsize = RLCPayloadSizeInBytes[ChannelCodingCS1]; // 20 bytes / block. int numbytes = 200; // Max PDU size is 1500; we will test 20*20 == 400 to start. int numblocks = numbytes / payloadsize; // The window size is only 64, so we would normlly have to wait for the ack before proceeding. // If we single stepped the MAC service loop while doing this, the dlservice routine // would do that. int bsn; int TFI = 1; // Use a fake tfi for this test. for (int j = 0; j < numblocks; j++) { if (randomize && j < numblocks-16) { // Goof up the order to see if blocks are reassembled in proper order. // I left the last few blocks alone to simplify. bsn = (j & 0xffff0) + ~(j & 0xf); } else { bsn = j; } int final = bsn == numblocks-1; RLCRawBlock *rawblock = fakeablock(bsn,TFI,final); // Raw uplink blocks are dequeued by processRLCUplinkDataBlock which // sends them to the uplink engine thusly: RLCUplinkDataBlock *rb = new RLCUplinkDataBlock(rawblock); //rb->text(os); // TODO: call processRLCUplinkDataBlock to test tfis delete rawblock; uptbf->engineRecvDataBlock(rb,0); // The final block has E bit set, which makes the RLCUpEngine call sendPDU(), // which sends the blocks to the BSSG, which puts them on our own queue. } gBSSG.mbsTestQ = NULL; // Restore BSSG to normal use. // Examine results on the testq.Get the block from the BSSG transmit queue. if (testQ.size() != 1) { os << "Unexpected BSSG Queue size="<getTLLI() != expected) { os << "ULUnitData msg wrong tlli=" << ulmsg->getTLLI() <getHeader(); expected = BSPDUType::UL_UNITDATA; if (ulhdr->mbuPDUType != expected) { os << "ULUnitData msg wrong PDUType=" << ulhdr->mbuPDUType <size(); expected = numbytes+hdrsize; if (msgsize != expected) { os << "BSSG UL UnitData msg wrong size" << LOGVAR(msgsize) << LOGVAR(expected)<<"\n"; } } // Check the data: expected = 0; int offset; int bads = 0; for (offset = 0; offset < numbytes; offset += sizeof(short), expected++) { int got = ulmsg->getUInt16(hdrsize+offset); if (got != expected) { os << "BSSG data wrong at "< 10) break; } } ulmsg->text(os); /*** os << "Data from beginning was:\n"; todo: dump the ulmsg directly. Use << this for ByteVector. offset = 0; for (int l = 0; l < 10; l++) { os << offset << ":"; for (int i = 0; i < 10; i++, offset+=2) { os <<" " <getUInt16(offset); } os << "\n"; } ***/ delete ulmsg; return 0; } #endif static int gprsDebug(int argc, char **argv, int argi, ostream&os) { if (argi < argc) { int newval = strtol(argv[argi++],NULL,0); // strtol allows hex gConfig.set("GPRS.Debug",newval); GPRSSetDebug(newval); } else if (! GPRSDebug) { //GPRSSetDebug(3); } char buf[100]; sprintf(buf,"GPRSDebug=0x%x\n",GPRSDebug); os << buf; return 0; } static int gprsSet(int argc, char **argv, int argi, ostream&os) { char *what = RN_CMD_ARG; if (!what) { return BAD_NUM_ARGS; } char *val = RN_CMD_ARG; // may be null. if (strmatch(what,"clock") || strmatch(what,"sync")) { if (val) { gFixSyncUseClock = atoi(val); } os << LOGVAR(gFixSyncUseClock) << "\n"; } else if (strmatch(what,"console")) { if (val) { gLogToConsole = atoi(val); } os << LOGVAR(gLogToConsole) << "\n"; } else { os << "gprs set: unrecognized argument: " << what << "\n"; } return 0; } static int gprsStep(int argc, char **argv, int argi, ostream&os) { if (!gL2MAC.macSingleStepMode) { os << "error: MAC is not in single step mode\n"; return 0; // disaster would ensue if we accidently started another serviceloop. } // We single step it ignoring the global clock, which // might result in messages from the channel service routines. ++gBSNNext; gL2MAC.macServiceLoop(); return 0; } static int gprsConsole(int argc, char **argv, int argi, ostream&os) { gLogToConsole = !gLogToConsole; // Default: toggle. if (argi < argc) { gLogToConsole = atoi(argv[argi++]); } os << "LogToConsole=" << gLogToConsole << "\n"; return 0; } static struct GprsSubCmds { const char *name; int (*subcmd)(int argc, char **argv, int argi,std::ostream&os); const char *syntax; } gprsSubCmds[] = { { "list",gprsList, "list [ms|tbf|ch] [-v] [-x] [-c] [id] # list active objects of specified type;\n\t\t -v => verbose; -c => include MS Capabilities -x => list expired rather than active" }, { "stat",gprsStats, "stat # Show GPRS statistics" }, { "free",gprsFree, "free ms|tbf|ch id # Delete something" }, { "freex",gprsFreeExpired, "freex # free expired ms and tbf structs" }, { "debug",gprsDebug, "debug [level] # Set debug level; 0 turns off" }, { "start",gprsStart, "start [step] # Start gprs, optionally in single-step-mode;\n\t\t- can also start by 'gprs rach'" }, { "stop",gprsStop, "stop [-c] # stop gprs thread and if -c release channels" }, { "step",gprsStep, "step # single step the MAC service loop (requires 'start step')." }, { "set",gprsSet, "set name [val] # print and optionally set a variable - see source for names" }, { "rach",gprsTestRach, "rach # Simulate a RACH, which starts gprs service" }, { "testmsg",gprsTestMsg, "testmsg # Test message functions" }, { "testbsn",gprsTestBSN, "testbsn # Test bsn<->frame number functions" }, #if INTERNAL_SGSN==0 { "testul",gprsTestUl, "testul [-r] # Send a test PDU through the RLCEngine; -r => randomize order " }, #endif { "console",gprsConsole, "console [0|1] # Send messages to console as well as /var/log/OpenBTS.log;\n\t\t (default=1 for debugging)" }, { "mem",gprsMem, "mem # Memory leak detector - print numbers of structs in use" }, { "test",gprsTest, "test # Temporary test" }, // Dont have the source code for pinghttp linked in yet. //{ "pinghttp",gprsPingHttp,"pinghttp address # Send an http request to address (dont use google.com)" }, // The "help" command is handled internally by gprsCLI. { NULL,NULL } }; static void help(std::ostream&os) { os << "gprs sub-commands to control GPRS radio mode. Syntax: gprs subcommand \n"; os << "subcommands are:\n"; struct GprsSubCmds *gscp; for (gscp = gprsSubCmds; gscp->name; gscp++) { os << "\t" << gscp->syntax; //if (gcp->arg) os << " " << gcp->arg; os << "\n"; } os << "Notes:\n"; os << " Downlink utilization averaged over 5 seconds; 1.0 means full utilization;\n"; os << " 2.0 means downlink requests exceeds available bandwidth by 2x, etc.\n"; } // Set defaults for gprs debugging. /******* static void debugdefaults() { static int inited = 0; if (!inited) { inited = 1; GPRSDebug = 3; gLogToConsole = 1; } } *******/ // Should return: SUCCESS (0), BAD_NUM_ARGS(1), BAD_VALUE(2), FAILURE (5) // but sadly, these are defined in CLI.cpp, so I guess we just return 0. // Note: argv includes command name so argc==1 implies no args. int gprsCLI(int argc, char **argv, std::ostream&os) { //debugdefaults(); ScopedLock lock(gL2MAC.macLock); if (argc <= 1) { help(os); return 1; } int argi = 1; // The number of arguments consumed so far; argv[0] was "gprs" char *subcmd = argv[argi++]; struct GprsSubCmds *gscp; int status = 0; // maybe success for (gscp = gprsSubCmds; gscp->name; gscp++) { if (0 == strcasecmp(subcmd,gscp->name)) { status = gscp->subcmd(argc,argv,argi,os); if (status == BAD_NUM_ARGS) { os << "wrong number of arguments\n"; } return status; //if (gscp->arg == NULL || (argi < argc && 0 == strcasecmp(gscp->arg,argv[argi+1]))) { //gscp->subcmd(argc,argv,argi + (gscp->arg?1:0),os); //} } } if (strcasecmp(subcmd,"help")) { os << "gprs: unrecognized sub-command: "<