Skip to content

Commit d40b833

Browse files
authored
V.24 Corrects for Call Stability (#117)
* cherrypick changes for V.24 fixes from tsawyer:dvmhost:codex/fix-v24-r05a05-prready; * finish review and cherrypick changes for FIFO configuration from tsawyer:dvmhost:codex/fix-v24-r05a05-prready; * based on Tims changes for writeP25Frame() instead of checking if the frame being written is greater than the available space then decrementing the m_p25Space, lets properly decrement the space and do an underflow check, because I am pretty confident that is the real problem here; * report underflows in debug mode only; * whoops should be LogDebugEx not LogError; * fix signedness;
1 parent d05a593 commit d40b833

4 files changed

Lines changed: 107 additions & 31 deletions

File tree

src/host/Host.Config.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
66
*
77
* Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL
8+
* Copyright (C) 2026 Timothy Sawyer, WD6AWP
89
*
910
*/
1011
#include "Defines.h"
@@ -511,6 +512,7 @@ bool Host::createModem()
511512
uint16_t dmrFifoLength = (uint16_t)modemConf["dmrFifoLength"].as<uint32_t>(DMR_TX_BUFFER_LEN);
512513
uint16_t p25FifoLength = (uint16_t)modemConf["p25FifoLength"].as<uint32_t>(P25_TX_BUFFER_LEN);
513514
uint16_t nxdnFifoLength = (uint16_t)modemConf["nxdnFifoLength"].as<uint32_t>(NXDN_TX_BUFFER_LEN);
515+
uint32_t v24P25TxQueueSize = p25FifoLength;
514516

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

@@ -645,6 +647,14 @@ bool Host::createModem()
645647
LogInfo(" DFSI FSC Initiator: %s", fscInitiator ? "yes" : "no");
646648
LogInfo(" DFSI TIA-102 Frames: %s", dfsiTIAMode ? "yes" : "no");
647649
}
650+
651+
// DFSI startup can enqueue a burst of timed frames before the modem
652+
// thread starts draining; keep the TX scheduler queue larger than the
653+
// raw modem FIFO to avoid clipping first-call onset.
654+
uint32_t minV24TxQueueSize = m_p25QueueSizeBytes + p25FifoLength;
655+
if (v24P25TxQueueSize < minV24TxQueueSize) {
656+
v24P25TxQueueSize = minV24TxQueueSize;
657+
}
648658
}
649659

650660
if (g_remoteModemMode) {
@@ -708,6 +718,8 @@ bool Host::createModem()
708718
LogInfo(" NXDN Queue Size: %u (%u bytes)", nxdnQueueSize, m_nxdnQueueSizeBytes);
709719
LogInfo(" DMR FIFO Size: %u bytes", dmrFifoLength);
710720
LogInfo(" P25 FIFO Size: %u bytes", p25FifoLength);
721+
if (m_isModemDFSI)
722+
LogInfo(" P25 DFSI TX Queue Size: %u bytes", v24P25TxQueueSize);
711723
LogInfo(" NXDN FIFO Size: %u bytes", nxdnFifoLength);
712724

713725
if (ignoreModemConfigArea) {
@@ -728,7 +740,7 @@ bool Host::createModem()
728740
}
729741

730742
if (m_isModemDFSI) {
731-
m_modem = new ModemV24(modemPort, m_duplex, m_p25QueueSizeBytes, p25FifoLength, rtrt, jitter,
743+
m_modem = new ModemV24(modemPort, m_duplex, m_p25QueueSizeBytes, v24P25TxQueueSize, rtrt, jitter,
732744
dumpModemStatus, displayModemDebugMessages, trace, debug);
733745
((ModemV24*)m_modem)->setCallTimeout(dfsiCallTimeout);
734746
((ModemV24*)m_modem)->setTIAFormat(dfsiTIAMode);

src/host/Host.P25.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
* GPLv2 Open Source. Use is subject to license terms.
55
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
66
*
7-
* Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL
7+
* Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL
8+
* Copyright (C) 2026 Timothy Sawyer, WD6AWP
89
*
910
*/
1011
#include "Defines.h"
@@ -255,6 +256,14 @@ void* Host::threadP25Writer(void* arg)
255256

256257
if (nextLen > 0U) {
257258
bool ret = host->m_modem->hasP25Space(nextLen);
259+
260+
// are we connected to a V.24 device? if so, always force
261+
// allow the write, V.24 modem status space can lag at call start; do not block
262+
// initial Net->RF voice frames on stale p25Space accounting
263+
if (host->m_modem->isV24Connected()) {
264+
ret = true;
265+
}
266+
258267
if (ret) {
259268
bool imm = false;
260269
uint32_t len = host->m_p25->getFrame(data, &imm);
@@ -267,7 +276,7 @@ void* Host::threadP25Writer(void* arg)
267276

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

272281
afterWriteCallback();
273282

src/host/modem/Modem.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
66
*
77
* Copyright (C) 2011-2021 Jonathan Naylor, G4KLX
8-
* Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL
8+
* Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL
99
* Copyright (C) 2021 Nat Moore
10+
* Copyright (C) 2026 Timothy Sawyer, WD6AWP
1011
*
1112
*/
1213
#include "Defines.h"
@@ -125,7 +126,7 @@ Modem::Modem(port::IModemPort* port, bool duplex, bool rxInvert, bool txInvert,
125126
m_nxdnFifoLength(NXDN_TX_BUFFER_LEN),
126127
m_adcOverFlowCount(0U),
127128
m_dacOverFlowCount(0U),
128-
m_v24Connected(true),
129+
m_v24Connected(false),
129130
m_modemState(STATE_IDLE),
130131
m_buffer(nullptr),
131132
m_length(0U),
@@ -754,7 +755,7 @@ void Modem::clock(uint32_t ms)
754755
// flag indicating if free space is being reported in 16-byte blocks instead of LDUs
755756
bool spaceInBlocks = (m_buffer[3U] & 0x80U) == 0x80U;
756757

757-
m_v24Connected = true;
758+
m_v24Connected = false;
758759
m_modemState = (DVM_STATE)m_buffer[4U];
759760

760761
m_tx = (m_buffer[5U] & 0x01U) == 0x01U;
@@ -1411,6 +1412,11 @@ bool Modem::writeDMRFrame1(const uint8_t* data, uint32_t length, bool imm)
14111412
}
14121413

14131414
m_dmrSpace1 -= length;
1415+
if ((int32_t)m_dmrSpace1 < 0) {
1416+
if (m_debug)
1417+
LogDebugEx(LOG_MODEM, "Modem::writeDMRFrame1()", "dmrSpace1 underflow, space = %u, length = %u", m_dmrSpace1, length);
1418+
m_dmrSpace1 = 0U;
1419+
}
14141420
}
14151421
else {
14161422
return false;
@@ -1465,6 +1471,11 @@ bool Modem::writeDMRFrame2(const uint8_t* data, uint32_t length, bool imm)
14651471
}
14661472

14671473
m_dmrSpace2 -= length;
1474+
if ((int32_t)m_dmrSpace2 < 0) {
1475+
if (m_debug)
1476+
LogDebugEx(LOG_MODEM, "Modem::writeDMRFrame2()", "dmrSpace2 underflow, space = %u, length = %u", m_dmrSpace2, length);
1477+
m_dmrSpace2 = 0U;
1478+
}
14681479
}
14691480
else {
14701481
return false;
@@ -1517,7 +1528,9 @@ bool Modem::writeP25Frame(const uint8_t* data, uint32_t length, bool imm)
15171528
uint32_t len = length + 2U;
15181529

15191530
// write or buffer P25 data to air interface
1520-
if (m_p25Space >= length) {
1531+
// for V.24/DFSI, the modem status space value may lag call startup; do
1532+
// not block initial Net->RF frames solely on stale p25Space
1533+
if (m_p25Space >= length || isV24Connected()) {
15211534
if (m_debug)
15221535
LogDebugEx(LOG_MODEM, "Modem::writeP25Frame()", "immediate write (len %u)", length);
15231536
if (m_trace)
@@ -1530,6 +1543,11 @@ bool Modem::writeP25Frame(const uint8_t* data, uint32_t length, bool imm)
15301543
}
15311544

15321545
m_p25Space -= length;
1546+
if ((int32_t)m_p25Space < 0) {
1547+
if (m_debug)
1548+
LogDebugEx(LOG_MODEM, "Modem::writeP25Frame()", "p25Space underflow, space = %u, length = %u", m_p25Space, length);
1549+
m_p25Space = 0U;
1550+
}
15331551
}
15341552
else {
15351553
return false;
@@ -1584,6 +1602,11 @@ bool Modem::writeNXDNFrame(const uint8_t* data, uint32_t length, bool imm)
15841602
}
15851603

15861604
m_nxdnSpace -= length;
1605+
if ((int32_t)m_nxdnSpace < 0) {
1606+
if (m_debug)
1607+
LogDebugEx(LOG_MODEM, "Modem::writeNXDNFrame()", "nxdnSpace underflow, space = %u, length = %u", m_nxdnSpace, length);
1608+
m_nxdnSpace = 0U;
1609+
}
15871610
}
15881611
else {
15891612
return false;

src/host/modem/ModemV24.cpp

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
* GPLv2 Open Source. Use is subject to license terms.
55
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
66
*
7-
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
7+
* Copyright (C) 2024-2026 Bryan Biedenkapp, N2PLL
88
* Copyright (C) 2024 Patrick McDonnell, W3AXL
9+
* Copyright (C) 2026 Timothy Sawyer, WD6AWP
910
*
1011
*/
1112
#include "Defines.h"
@@ -394,6 +395,14 @@ void ModemV24::clock(uint32_t ms)
394395
int len = 0;
395396

396397
// write anything waiting to the serial port
398+
399+
/*
400+
** bryanb: Tim's fork changes the order of this, however, this is a terrible idea becuase it will ultimately break
401+
** the intended purpose of this code which is to allow prioritized frames, reasonably speaking this code should
402+
** not cause problems for timing -- but we should keep an eye on this (if its problematic for some reason,
403+
** we can consider perhaps adding a flag that disables the immediate queue, forcing all packets in to the normal
404+
** queue)
405+
*/
397406
if (!m_txImmP25Queue.isEmpty())
398407
len = writeSerial(&m_txImmP25Queue);
399408
else
@@ -477,6 +486,8 @@ int ModemV24::writeSerial(RingBuffer<uint8_t>* queue)
477486
* | Length | Tag | int64_t timestamp in ms | data |
478487
*/
479488

489+
std::lock_guard<std::mutex> lock(m_txP25QueueLock);
490+
480491
// check empty
481492
if (queue->isEmpty())
482493
return 0U;
@@ -486,24 +497,19 @@ int ModemV24::writeSerial(RingBuffer<uint8_t>* queue)
486497
::memset(length, 0x00U, 2U);
487498
queue->peek(length, 2U);
488499

489-
// convert length byets to int
500+
// convert length bytes to int
490501
uint16_t len = 0U;
491502
len = (length[0U] << 8) + length[1U];
492503

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

499-
// check available modem space
500-
if (m_p25Space < len)
501-
return 0U;
502-
503-
std::lock_guard<std::mutex> lock(m_txP25QueueLock);
504-
505510
// get current timestamp
506511
int64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
512+
int64_t ts = 0L;
507513

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

514520
// get the timestamp
515-
int64_t ts;
516521
assert(sizeof ts == 8);
517522
::memcpy(&ts, lengthTagTs + 3U, 8U);
518523

@@ -524,23 +529,34 @@ int ModemV24::writeSerial(RingBuffer<uint8_t>* queue)
524529

525530
// check if we have enough data to get everything - len + 2U (length bytes) + 1U (tag) + 8U (timestamp)
526531
if (queue->dataSize() >= len + 11U) {
527-
// Get the length, tag and timestamp
528-
uint8_t lengthTagTs[11U];
529-
queue->get(lengthTagTs, 11U);
530-
531-
// Get the actual data
532-
DECLARE_UINT8_ARRAY(buffer, len);
533-
queue->get(buffer, len);
534-
535-
// Sanity check on data tag
532+
// peek the full entry so we can keep it queued if a write fails
533+
DECLARE_UINT8_ARRAY(entry, len + 11U);
534+
queue->peek(entry, len + 11U);
535+
536+
// get the length, tag, timestamp and payload pointers from peeked data
537+
uint8_t* lengthTagTs = entry;
538+
uint8_t* buffer = entry + 11U;
539+
540+
// sanity check on data tag
536541
uint8_t tag = lengthTagTs[2U];
537542
if (tag != TAG_DATA) {
538543
LogError(LOG_MODEM, "Got unexpected data tag from TX P25 ringbuffer! %02X", tag);
544+
545+
// drop malformed entry so we can recover queue alignment
546+
DECLARE_UINT8_ARRAY(discard, len + 11U);
547+
queue->get(discard, len + 11U);
539548
return 0U;
540549
}
541550

542-
// we already checked the timestamp above, so we just get the data and write it
543-
return m_port->write(buffer, len);
551+
// we already checked the timestamp above, so we just write it
552+
int ret = m_port->write(buffer, len);
553+
if (ret > 0) {
554+
// only remove an entry once it was written successfully
555+
DECLARE_UINT8_ARRAY(discard, len + 11U);
556+
queue->get(discard, len + 11U);
557+
}
558+
559+
return ret;
544560
}
545561

546562
return 0U;
@@ -2363,12 +2379,19 @@ bool ModemV24::queueP25Frame(uint8_t* data, uint16_t len, SERIAL_TX_TYPE msgType
23632379
std::lock_guard<std::mutex> lock(m_txP25QueueLock);
23642380

23652381
// check available ringbuffer space
2382+
// keep one byte of headroom to avoid the ring buffer "full == empty"
2383+
// ambiguity (iPtr == oPtr) when a write would consume exactly all free space
2384+
uint32_t needed = len + 11U;
23662385
if (imm) {
2367-
if (m_txImmP25Queue.freeSpace() < (len + 11U))
2386+
uint32_t free = m_txImmP25Queue.freeSpace();
2387+
if (free <= needed) {
23682388
return false;
2389+
}
23692390
} else {
2370-
if (m_txP25Queue.freeSpace() < (len + 11U))
2391+
uint32_t free = m_txP25Queue.freeSpace();
2392+
if (free <= needed) {
23712393
return false;
2394+
}
23722395
}
23732396

23742397
// convert 16-bit length to 2 bytes
@@ -2429,6 +2452,10 @@ void ModemV24::startOfStreamV24(const p25::lc::LC& control)
24292452
{
24302453
m_txCallInProgress = true;
24312454

2455+
// start each Net->RF stream with a fresh scheduler epoch so a new call
2456+
// doesn't inherit a future-biased timestamp from the previous call
2457+
m_lastP25Tx = 0U;
2458+
24322459
MotStartOfStream start = MotStartOfStream();
24332460
start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE);
24342461
start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD);
@@ -2546,6 +2573,10 @@ void ModemV24::startOfStreamTIA(const p25::lc::LC& control)
25462573
m_txCallInProgress = true;
25472574
m_superFrameCnt = 1U;
25482575

2576+
// start each Net->RF stream with a fresh scheduler epoch so a new call
2577+
// doesn't inherit a future-biased timestamp from the previous call
2578+
m_lastP25Tx = 0U;
2579+
25492580
p25::lc::LC lc = p25::lc::LC(control);
25502581

25512582
uint16_t length = 0U;
@@ -2806,7 +2837,8 @@ void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length, bool imm)
28062837
break;
28072838

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

28122844
case DUID::TDULC:

0 commit comments

Comments
 (0)