Skip to content
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ _deps
# Ignore thumbnails created by windows
Thumbs.db

# Ignore macOS Finder metadata files
.DS_Store

# Ignore files created by clang-check
*.plist

Expand Down
13 changes: 12 additions & 1 deletion src/host/Host.Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ bool Host::createModem()
uint16_t dmrFifoLength = (uint16_t)modemConf["dmrFifoLength"].as<uint32_t>(DMR_TX_BUFFER_LEN);
uint16_t p25FifoLength = (uint16_t)modemConf["p25FifoLength"].as<uint32_t>(P25_TX_BUFFER_LEN);
uint16_t nxdnFifoLength = (uint16_t)modemConf["nxdnFifoLength"].as<uint32_t>(NXDN_TX_BUFFER_LEN);
uint32_t v24P25TxQueueSize = p25FifoLength;

yaml::Node dfsiParams = modemConf["dfsi"];

Expand Down Expand Up @@ -645,6 +646,14 @@ bool Host::createModem()
LogInfo(" DFSI FSC Initiator: %s", fscInitiator ? "yes" : "no");
LogInfo(" DFSI TIA-102 Frames: %s", dfsiTIAMode ? "yes" : "no");
}

// DFSI startup can enqueue a burst of timed frames before the modem
// thread starts draining; keep the TX scheduler queue larger than the
// raw modem FIFO to avoid clipping first-call onset.
uint32_t minV24TxQueueSize = m_p25QueueSizeBytes + p25FifoLength;
if (v24P25TxQueueSize < minV24TxQueueSize) {
v24P25TxQueueSize = minV24TxQueueSize;
}
}

if (g_remoteModemMode) {
Expand Down Expand Up @@ -708,6 +717,8 @@ bool Host::createModem()
LogInfo(" NXDN Queue Size: %u (%u bytes)", nxdnQueueSize, m_nxdnQueueSizeBytes);
LogInfo(" DMR FIFO Size: %u bytes", dmrFifoLength);
LogInfo(" P25 FIFO Size: %u bytes", p25FifoLength);
if (m_isModemDFSI)
LogInfo(" P25 DFSI TX Queue Size: %u bytes", v24P25TxQueueSize);
LogInfo(" NXDN FIFO Size: %u bytes", nxdnFifoLength);

if (ignoreModemConfigArea) {
Expand All @@ -728,7 +739,7 @@ bool Host::createModem()
}

if (m_isModemDFSI) {
m_modem = new ModemV24(modemPort, m_duplex, m_p25QueueSizeBytes, p25FifoLength, rtrt, jitter,
m_modem = new ModemV24(modemPort, m_duplex, m_p25QueueSizeBytes, v24P25TxQueueSize, rtrt, jitter,
dumpModemStatus, displayModemDebugMessages, trace, debug);
((ModemV24*)m_modem)->setCallTimeout(dfsiCallTimeout);
((ModemV24*)m_modem)->setTIAFormat(dfsiTIAMode);
Expand Down
7 changes: 6 additions & 1 deletion src/host/Host.P25.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ void* Host::threadP25Writer(void* arg)

if (nextLen > 0U) {
bool ret = host->m_modem->hasP25Space(nextLen);
// V.24 modem status space can lag at call start; do not block
// initial Net->RF voice frames on stale p25Space accounting.
if (!ret && host->m_modem->isV24Connected()) {
ret = true;
}
if (ret) {
bool imm = false;
uint32_t len = host->m_p25->getFrame(data, &imm);
Expand All @@ -267,7 +272,7 @@ void* Host::threadP25Writer(void* arg)

// if the state is P25; write P25 frame data
if (host->m_state == STATE_P25) {
host->m_modem->writeP25Frame(data, len);
host->m_modem->writeP25Frame(data, len, imm);

afterWriteCallback();

Expand Down
9 changes: 7 additions & 2 deletions src/host/modem/Modem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1517,7 +1517,10 @@ bool Modem::writeP25Frame(const uint8_t* data, uint32_t length, bool imm)
uint32_t len = length + 2U;

// write or buffer P25 data to air interface
if (m_p25Space >= length) {
//
// For V.24/DFSI, the modem status space value may lag call startup; do
// not block initial Net->RF frames solely on stale p25Space.
if (m_p25Space >= length || isV24Connected()) {
if (m_debug)
LogDebugEx(LOG_MODEM, "Modem::writeP25Frame()", "immediate write (len %u)", length);
if (m_trace)
Expand All @@ -1529,7 +1532,9 @@ bool Modem::writeP25Frame(const uint8_t* data, uint32_t length, bool imm)
return false;
}

m_p25Space -= length;
if (m_p25Space >= length) {
m_p25Space -= length;
}
}
else {
return false;
Expand Down
72 changes: 47 additions & 25 deletions src/host/modem/ModemV24.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,11 @@ void ModemV24::clock(uint32_t ms)
int len = 0;

// write anything waiting to the serial port
if (!m_txImmP25Queue.isEmpty())
//
// Keep normal queue first for startup consistency with older behavior.
len = writeSerial(&m_txP25Queue);
if (len == 0 && !m_txImmP25Queue.isEmpty())
len = writeSerial(&m_txImmP25Queue);
else
len = writeSerial(&m_txP25Queue);
if (m_debug && len > 0) {
LogDebug(LOG_MODEM, "Wrote %u-byte message to the serial V24 device", len);
} else if (len < 0) {
Expand Down Expand Up @@ -477,6 +478,8 @@ int ModemV24::writeSerial(RingBuffer<uint8_t>* queue)
* | Length | Tag | int64_t timestamp in ms | data |
*/

std::lock_guard<std::mutex> lock(m_txP25QueueLock);

// check empty
if (queue->isEmpty())
return 0U;
Expand All @@ -486,24 +489,19 @@ int ModemV24::writeSerial(RingBuffer<uint8_t>* queue)
::memset(length, 0x00U, 2U);
queue->peek(length, 2U);

// convert length byets to int
// convert length bytes to int
uint16_t len = 0U;
len = (length[0U] << 8) + length[1U];

// this ensures we never get in a situation where we have length & type bytes stuck in the queue by themselves
// this ensures we never get in a situation where we have length bytes stuck in the queue by themselves
if (queue->dataSize() == 2U && len > queue->dataSize()) {
queue->get(length, 2U); // ensure we pop bytes off
return 0U;
}

// check available modem space
if (m_p25Space < len)
return 0U;

std::lock_guard<std::mutex> lock(m_txP25QueueLock);

// get current timestamp
int64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
int64_t ts = 0L;

// peek the timestamp to see if we should wait
if (queue->dataSize() >= 11U) {
Expand All @@ -512,7 +510,6 @@ int ModemV24::writeSerial(RingBuffer<uint8_t>* queue)
queue->peek(lengthTagTs, 11U);

// get the timestamp
int64_t ts;
assert(sizeof ts == 8);
::memcpy(&ts, lengthTagTs + 3U, 8U);

Expand All @@ -524,23 +521,33 @@ int ModemV24::writeSerial(RingBuffer<uint8_t>* queue)

// check if we have enough data to get everything - len + 2U (length bytes) + 1U (tag) + 8U (timestamp)
if (queue->dataSize() >= len + 11U) {
// Get the length, tag and timestamp
uint8_t lengthTagTs[11U];
queue->get(lengthTagTs, 11U);
// Get the actual data
DECLARE_UINT8_ARRAY(buffer, len);
queue->get(buffer, len);
// Peek the full entry so we can keep it queued if a write fails.
DECLARE_UINT8_ARRAY(entry, len + 11U);
queue->peek(entry, len + 11U);

// Get the length, tag, timestamp and payload pointers from peeked data.
uint8_t* lengthTagTs = entry;
uint8_t* buffer = entry + 11U;

// Sanity check on data tag
uint8_t tag = lengthTagTs[2U];
if (tag != TAG_DATA) {
LogError(LOG_MODEM, "Got unexpected data tag from TX P25 ringbuffer! %02X", tag);
// Drop malformed entry so we can recover queue alignment.
DECLARE_UINT8_ARRAY(discard, len + 11U);
queue->get(discard, len + 11U);
return 0U;
}

// we already checked the timestamp above, so we just get the data and write it
return m_port->write(buffer, len);
// we already checked the timestamp above, so we just write it
int ret = m_port->write(buffer, len);
if (ret > 0) {
// Only remove an entry once it was written successfully.
DECLARE_UINT8_ARRAY(discard, len + 11U);
queue->get(discard, len + 11U);
}

return ret;
}

return 0U;
Expand Down Expand Up @@ -2362,13 +2369,21 @@ bool ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType

std::lock_guard<std::mutex> lock(m_txP25QueueLock);

// check available ringbuffer space
// check available ringbuffer space.
//
// Keep one byte of headroom to avoid the ring buffer "full == empty"
// ambiguity (iPtr == oPtr) when a write would consume exactly all free space.
uint32_t needed = len + 11U;
if (imm) {
if (m_txImmP25Queue.freeSpace() < (len + 11U))
uint32_t free = m_txImmP25Queue.freeSpace();
if (free <= needed) {
return false;
}
} else {
if (m_txP25Queue.freeSpace() < (len + 11U))
uint32_t free = m_txP25Queue.freeSpace();
if (free <= needed) {
return false;
}
}

// convert 16-bit length to 2 bytes
Expand Down Expand Up @@ -2428,6 +2443,9 @@ bool ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType
void ModemV24::startOfStreamV24(const p25::lc::LC& control)
{
m_txCallInProgress = true;
// Start each Net->RF stream with a fresh scheduler epoch so a new call
// doesn't inherit a future-biased timestamp from the previous call.
m_lastP25Tx = 0U;

MotStartOfStream start = MotStartOfStream();
start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE);
Expand Down Expand Up @@ -2545,6 +2563,9 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control)
{
m_txCallInProgress = true;
m_superFrameCnt = 1U;
// Start each Net->RF stream with a fresh scheduler epoch so a new call
// doesn't inherit a future-biased timestamp from the previous call.
m_lastP25Tx = 0U;

p25::lc::LC lc = p25::lc::LC(control);

Expand Down Expand Up @@ -2806,7 +2827,8 @@ void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length, bool imm)
break;

case DUID::TDU:
endOfStreamV24(); // this may incorrectly sent STOP ICW's with the VOICE payload, but it's better than nothing for now
if (m_txCallInProgress)
endOfStreamV24();
break;

case DUID::TDULC:
Expand Down