fixeria has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-trx/+/43116?usp=email )
Change subject: Transceiver52M: implement TRXDv2 support ......................................................................
Transceiver52M: implement TRXDv2 support
TRXDv2 was proposed and implemented in osmo-bts a while ago, but osmo-trx was still stuck at TRXDv1. This patch implements TRXDv2 and UL/DL burst batching using libosmo-trx API.
DL: driveTxPriorityQueue() now loops over osmo_trxd_burst_req_parse() to handle datagrams potentially containing multiple batched BURST.req, dispatching each via the new handleBurstReq().
UL: accumulate BURST.ind PDUs for the same FN into one msgb per channel and flush as a single datagram once the next frame's PDU arrives or the channel stops; TRXDv0/v1 channels keep sending one PDU per datagram via sendBurstInd(). Batching is unconditional for now; making it configurable is left as a follow-up.
Change-Id: Ie6a33e3980b3ca4ab05a3d4221d5a8ba0132233d Related: OS#5283 --- M Transceiver52M/Transceiver.cpp M Transceiver52M/Transceiver.h 2 files changed, 172 insertions(+), 52 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-trx refs/changes/16/43116/1
diff --git a/Transceiver52M/Transceiver.cpp b/Transceiver52M/Transceiver.cpp index 20a568a..2753c47 100644 --- a/Transceiver52M/Transceiver.cpp +++ b/Transceiver52M/Transceiver.cpp @@ -56,6 +56,13 @@ /* Number of running values use in noise average */ #define NOISE_CNT 20
+/* Worst-case size of a batched TRXDv2 datagram (either direction): up to 8 + * timeslots per frame, each sized as generously as a lone PDU + * (OSMO_TRXD_MSG_BUF_SIZE already covers header + max burst payload for one + * PDU; PDUs after the first omit the 4-byte FN, so this is a safe + * over-estimate, not a tight bound). */ +#define TRX_TRXD_BATCH_BUF_SIZE (8 * OSMO_TRXD_MSG_BUF_SIZE) +
static void dispatch_trx_rate_ctr_change(TransceiverState *state, unsigned int chan) { thread_enable_cancel(false); @@ -142,7 +149,9 @@ mCtrlSockets(mChans), mClockSocket(-1), mTxPriorityQueues(mChans), mReceiveFIFO(mChans), mRxServiceLoopThreads(mChans), mRxLowerLoopThread(nullptr), mTxLowerLoopThread(nullptr), - mTxPriorityQueueServiceLoopThreads(mChans), mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface), + mTxPriorityQueueServiceLoopThreads(mChans), + mBurstIndBatch(mChans), mBurstIndBatchFn(mChans), + mTransmitLatency(wTransmitLatency), mRadioInterface(wRadioInterface), mOn(false),mForceClockInterface(false), mTxFreq(0.0), mRxFreq(0.0), mTSC(0), mMaxExpectedDelayAB(0), mMaxExpectedDelayNB(0), mWriteBurstToDiskMask(0), mVersionTRXD(mChans), mStates(mChans) { @@ -168,6 +177,7 @@ mTxPriorityQueues[i].clear(); if (mDataSockets[i] >= 0) close(mDataSockets[i]); + msgb_free(mBurstIndBatch[i]); } }
@@ -366,6 +376,9 @@ delete mRxServiceLoopThreads[i]; delete mTxPriorityQueueServiceLoopThreads[i];
+ /* Rx thread is joined, so no concurrent access to the batch anymore */ + flushBurstIndBatch(i); + mTxPriorityQueues[i].clear(); }
@@ -1080,29 +1093,9 @@ return 0; }
-bool Transceiver::driveTxPriorityQueue(size_t chan) +bool Transceiver::handleBurstReq(size_t chan, const struct osmo_trxd_burst_req *br) { - char buffer[OSMO_TRXD_MSG_BUF_SIZE]; - int msgLen; - struct osmo_trxd_parse_state st; - struct osmo_trxd_burst_req br; - int rc; - - // check data socket - msgLen = read(mDataSockets[chan], buffer, sizeof(buffer)); - if (msgLen <= 0) { - LOGCHAN(chan, DTRXDDL, NOTICE) << "mDataSockets read(" << mDataSockets[chan] << ") failed: " << msgLen; - return false; - } - - osmo_trxd_parse_state_init(&st); - rc = osmo_trxd_burst_req_parse(&st, &br, (const uint8_t *) buffer, msgLen); - if (rc < 0) { - LOGCHAN(chan, DTRXDDL, ERROR) << "failed to parse BURST.req (rc=" << rc << ")"; - return false; - } - - switch (br.burst_len) { + switch (br->burst_len) { case gSlotLen: /* GSM burst */ break; case EDGE_BURST_NBITS: /* EDGE burst */ @@ -1113,19 +1106,19 @@ break; default: LOGCHAN(chan, DTRXDDL, ERROR) << "badly formatted packet on GSM->TRX interface (burst_len=" - << br.burst_len << ")"; + << br->burst_len << ")"; return false; }
- LOGCHAN(chan, DTRXDDL, DEBUG) << "Rx TRXD message: fn=" << br.fn << ", tn=" << unsigned(br.tn) - << ", burst_len=" << br.burst_len; + LOGCHAN(chan, DTRXDDL, DEBUG) << "Rx TRXD message: fn=" << br->fn << ", tn=" << unsigned(br->tn) + << ", burst_len=" << br->burst_len;
TransceiverState *state = &mStates[chan]; - GSM::Time currTime = GSM::Time(br.fn, br.tn); + GSM::Time currTime = GSM::Time(br->fn, br->tn);
/* Verify proper FN order in DL stream */ - if (state->first_dl_fn_rcv[br.tn]) { - int32_t delta = GSM::FNDelta(currTime.FN(), state->last_dl_time_rcv[br.tn].FN()); + if (state->first_dl_fn_rcv[br->tn]) { + int32_t delta = GSM::FNDelta(currTime.FN(), state->last_dl_time_rcv[br->tn].FN()); if (delta == 1) { /* usual expected scenario, continue code flow */ } else if (delta == 0) { @@ -1135,7 +1128,7 @@ return true; } else if (delta < 0) { LOGCHAN(chan, DTRXDDL, INFO) << "Rx TRXD msg with previous FN " << currTime - << " vs last " << state->last_dl_time_rcv[br.tn]; + << " vs last " << state->last_dl_time_rcv[br->tn]; state->ctrs.tx_trxd_fn_outoforder++; dispatch_trx_rate_ctr_change(state, chan); /* Allow adding radio vector below, since it gets sorted in the queue */ @@ -1145,25 +1138,66 @@ * setups. Also, osmo-trx supports optionally filling empty bursts on * its own. In that case bts-trx is not obliged to submit all bursts. */ LOGCHAN(chan, DTRXDDL, INFO) << "Rx TRXD msg with future FN " << currTime - << " vs last " << state->last_dl_time_rcv[br.tn] + << " vs last " << state->last_dl_time_rcv[br->tn] << ", " << delta - 1 << " FN lost"; state->ctrs.tx_trxd_fn_skipped += delta - 1; dispatch_trx_rate_ctr_change(state, chan); } if (delta > 0) - state->last_dl_time_rcv[br.tn] = currTime; + state->last_dl_time_rcv[br->tn] = currTime; } else { /* Initial check, simply store state */ - state->first_dl_fn_rcv[br.tn] = true; - state->last_dl_time_rcv[br.tn] = currTime; + state->first_dl_fn_rcv[br->tn] = true; + state->last_dl_time_rcv[br->tn] = currTime; }
- BitVector newBurst(br.burst_len); + BitVector newBurst(br->burst_len); BitVector::iterator itr = newBurst.begin(); - const ubit_t *bufferItr = br.burst; + const ubit_t *bufferItr = br->burst; while (itr < newBurst.end()) *itr++ = *bufferItr++;
- addRadioVector(chan, newBurst, br.att, currTime); + addRadioVector(chan, newBurst, br->att, currTime); + + return true; +} + +bool Transceiver::driveTxPriorityQueue(size_t chan) +{ + char buffer[TRX_TRXD_BATCH_BUF_SIZE]; + int msgLen; + struct osmo_trxd_parse_state st; + struct osmo_trxd_burst_req br; + const uint8_t *pos; + size_t remain; + int rc; + + // check data socket + msgLen = read(mDataSockets[chan], buffer, sizeof(buffer)); + if (msgLen <= 0) { + LOGCHAN(chan, DTRXDDL, NOTICE) << "mDataSockets read(" << mDataSockets[chan] << ") failed: " << msgLen; + return false; + } + + /* Normally a single PDU per datagram (TRXDv0/v1, or unbatched TRXDv2), + * but the BTS side may also send us a TRXDv2 batch (multiple PDUs for + * the same FN, one per timeslot): keep parsing while BATCH.ind is set. */ + osmo_trxd_parse_state_init(&st); + pos = (const uint8_t *) buffer; + remain = (size_t) msgLen; + + do { + rc = osmo_trxd_burst_req_parse(&st, &br, pos, remain); + if (rc < 0) { + LOGCHAN(chan, DTRXDDL, ERROR) << "failed to parse BURST.req (rc=" << rc << ")"; + return false; + } + + if (!handleBurstReq(chan, &br)) + return false; + + pos += rc; + remain -= rc; + } while (br.flags & OSMO_TRXD_F_BATCH_IND);
return true; } @@ -1213,28 +1247,17 @@ << " bits: " << os; }
-bool Transceiver::driveReceiveFIFO(size_t chan) + +bool Transceiver::sendBurstInd(size_t chan, const struct osmo_trxd_burst_ind *bi) { - struct osmo_trxd_burst_ind bi; struct msgb *msg; int rc;
- if ((rc = pullRadioVector(chan, &bi)) < 0) { - if (rc == -ENOENT) { /* timeslot off, continue processing */ - LOGCHAN(chan, DTRXDUL, DEBUG) << unsigned(bi.tn) << ":" << bi.fn << " timeslot is off"; - return true; - } - return false; /* other errors: we want to stop the process */ - } - - if (!(bi.flags & OSMO_TRXD_F_NOPE_IND) && log_check_level(DTRXDUL, LOGL_DEBUG)) - logRxBurst(chan, &bi); - msg = msgb_alloc(OSMO_TRXD_MSG_BUF_SIZE, "trxd_burst_ind"); if (!msg) return false;
- rc = osmo_trxd_burst_ind_build(msg, mVersionTRXD[chan], &bi); + rc = osmo_trxd_burst_ind_build(msg, mVersionTRXD[chan], bi); if (rc < 0) { LOGCHAN(chan, DTRXDUL, ERROR) << "failed to build BURST.ind (rc=" << rc << ")"; msgb_free(msg); @@ -1252,6 +1275,81 @@ return true; }
+bool Transceiver::flushBurstIndBatch(size_t chan) +{ + struct msgb *msg = mBurstIndBatch[chan]; + int rc; + + if (!msg || msgb_length(msg) == 0) + return true; /* nothing accumulated */ + + osmo_trxd_build_fin(msg, mVersionTRXD[chan]); + + rc = write(mDataSockets[chan], msgb_data(msg), msgb_length(msg)); + msgb_trim(msg, 0); + if (rc < 0) { + LOGCHAN(chan, DTRXDUL, NOTICE) << "mDataSockets write(" << mDataSockets[chan] << ") failed: " << rc; + return false; + } + + return true; +} + +bool Transceiver::queueBurstIndBatched(size_t chan, const struct osmo_trxd_burst_ind *bi) +{ + struct msgb *msg = mBurstIndBatch[chan]; + int rc; + + if (msg && msgb_length(msg) > 0 && mBurstIndBatchFn[chan] != bi->fn) { + /* previous frame's batch is complete (a PDU for a new FN showed up) */ + if (!flushBurstIndBatch(chan)) + return false; + } + + if (!msg) { + msg = msgb_alloc(TRX_TRXD_BATCH_BUF_SIZE, "trxd_burst_ind_batch"); + if (!msg) + return false; + mBurstIndBatch[chan] = msg; + } + + if (msgb_length(msg) == 0) + mBurstIndBatchFn[chan] = bi->fn; + + rc = osmo_trxd_burst_ind_build(msg, mVersionTRXD[chan], bi); + if (rc < 0) { + LOGCHAN(chan, DTRXDUL, ERROR) << "failed to build batched BURST.ind (rc=" << rc << ")"; + msgb_trim(msg, 0); + return false; + } + + return true; +} + +bool Transceiver::driveReceiveFIFO(size_t chan) +{ + struct osmo_trxd_burst_ind bi; + int rc; + + if ((rc = pullRadioVector(chan, &bi)) < 0) { + if (rc == -ENOENT) { /* timeslot off, continue processing */ + LOGCHAN(chan, DTRXDUL, DEBUG) << unsigned(bi.tn) << ":" << bi.fn << " timeslot is off"; + return true; + } + return false; /* other errors: we want to stop the process */ + } + + if (!(bi.flags & OSMO_TRXD_F_NOPE_IND) && log_check_level(DTRXDUL, LOGL_DEBUG)) + logRxBurst(chan, &bi); + + /* TODO: make batching configurable via VTY, so it can be disabled even + * when TRXDv2 is negotiated (e.g. to trade datagram count for latency) */ + if (mVersionTRXD[chan] < 2) + return sendBurstInd(chan, &bi); + + return queueBurstIndBatched(chan, &bi); +} + void Transceiver::driveTxFIFO() {
diff --git a/Transceiver52M/Transceiver.h b/Transceiver52M/Transceiver.h index 8d27db9..e540c9f 100644 --- a/Transceiver52M/Transceiver.h +++ b/Transceiver52M/Transceiver.h @@ -44,7 +44,7 @@ extern Transceiver *transceiver;
/* The latest TRXD header format version advertised/accepted by this TRX implementation */ -#define TRX_DATA_FORMAT_VER 1 +#define TRX_DATA_FORMAT_VER 2
/** Channel descriptor for transceiver object and channel number pair */ struct TrxChanThParams { @@ -186,6 +186,15 @@ Thread *mTxLowerLoopThread; ///< thread to push bursts into transmit FIFO std::vector<Thread *> mTxPriorityQueueServiceLoopThreads; ///< thread to process transmit bursts from GSM core
+ /* TRXDv2 uplink PDU batching (see osmo-gsm-manuals trx_if.adoc, combination + * "b"): all BURST.ind PDUs for one TDMA frame on a given channel are + * accumulated into a single datagram, flushed once the next frame's PDU + * arrives (i.e. keyed off a change in FN, not off tn==7, since not every + * timeslot is necessarily active). Batching across channels (TRXN) is not + * supported. Unused (kept NULL) for channels negotiated at TRXDv0/v1. */ + std::vector<struct msgb *> mBurstIndBatch; + std::vector<uint32_t> mBurstIndBatchFn; + GSM::Time mTransmitLatency; ///< latency between basestation clock and transmit deadline clock GSM::Time mLatencyUpdateTime; ///< last time latency was updated GSM::Time mTransmitDeadlineClock; ///< deadline for pushing bursts into transmit FIFO @@ -208,6 +217,19 @@ /** Pull and demodulate a burst from the receive FIFO */ int pullRadioVector(size_t chan, struct osmo_trxd_burst_ind *ind);
+ /** Send one BURST.ind PDU as its own datagram (TRXDv0/v1) */ + bool sendBurstInd(size_t chan, const struct osmo_trxd_burst_ind *bi); + + /** Append one BURST.ind PDU to the per-channel TRXDv2 batch, flushing the + * previous frame's batch first if this PDU belongs to a new frame */ + bool queueBurstIndBatched(size_t chan, const struct osmo_trxd_burst_ind *bi); + + /** Write out and reset the accumulated TRXDv2 batch for a channel, if any */ + bool flushBurstIndBatch(size_t chan); + + /** Handle one parsed BURST.req PDU (one out of a batch, or a lone one) */ + bool handleBurstReq(size_t chan, const struct osmo_trxd_burst_req *br); + /** Set modulus for specific timeslot */ void setModulus(size_t timeslot, size_t chan);