From a37958b963012fe248f63a38ec3765c17ee6e4f1 Mon Sep 17 00:00:00 2001 From: Wes Biggs Date: Thu, 29 Oct 2026 17:53:58 -0500 Subject: [PATCH 1/2] SwiftLink: never block in client.write(); send Tx bytes in bursts NativeEthernet's socketSend() busy-waits until the send buffer has room, so a large upload from the C64 stalled loop() inside client.write() until the stack freed space (for good if the stack is polled from loop()). Collect Tx bytes in a 256-byte buffer, send it after a 2 ms pause, after 20 ms or when full, and never write more than availableForWrite(). While the buffer is full Tx-empty stays clear, so the C64 waits as it would on a 6551. One segment per burst instead of one per byte. Co-Authored-By: Claude Opus 5.5 --- .../Common/IO_Handlers/IOH_Swiftlink.c | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_Swiftlink.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_Swiftlink.c index adca1840..6f80b525 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_Swiftlink.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_Swiftlink.c @@ -179,6 +179,16 @@ volatile uint8_t SwiftRegStatus, SwiftRegCommand, SwiftRegControl, TurboRegEnhan uint8_t PlusCount; uint32_t C64CycBetweenRx, LastTxMillis = millis(); +//Tx bytes from the C64 are collected here and sent with one client.write() per burst (one TCP segment per byte before), and only as +// much at a time as the stack has room for: client.write() busy-waits for send buffer space, which stalls the main loop until the +// stack frees some (and for good when the stack is polled from the main loop). While this buffer is full the C64 sees Tx not empty. +#define SwiftTxOutSize 256 +#define SwiftTxOutIdleMicros 2000 //send once the C64 has sent nothing for this long, +#define SwiftTxOutMaxMicros 20000 // or this long after the oldest byte waiting +DMAMEM uint8_t SwiftTxOut[SwiftTxOutSize]; +uint16_t SwiftTxOutLen = 0; +uint32_t SwiftTxOutFirstMicros, SwiftTxOutLastMicros; + // Browser mode: Buffer saved in ASCII from host, converted before sending out // Uses Send...Immediate commands for direct output @@ -403,6 +413,7 @@ void InitHndlr_SwiftLink() TxMsgOffset =0; PrintingHyperlink = false; PlusCount=0; + SwiftTxOutLen = 0; FreeDriveDirMenu(); //clear out drive menu to make space in RAM2 // RAM2 usage as of 11/7/23: @@ -522,6 +533,21 @@ void IO1Hndlr_SwiftLink(uint8_t Address, bool R_Wn) } } +void SendSwiftTxOut() +{ //send what the stack has room for now (never more: client.write() would wait for the rest), the rest on a later poll + if (!client.connected()) + { + SwiftTxOutLen = 0; //connection gone + return; + } + int Room = client.availableForWrite(); + if (Room <= 0) return; + uint16_t Sent = client.write(SwiftTxOut, (Room < SwiftTxOutLen) ? Room : SwiftTxOutLen); + if (Sent == 0) return; + SwiftTxOutLen -= Sent; + memmove(SwiftTxOut, SwiftTxOut + Sent, SwiftTxOutLen); +} + void PollingHndlr_SwiftLink() { //detect connection change @@ -576,14 +602,20 @@ void PollingHndlr_SwiftLink() while (client.available()) AddRawCharToRxQueue(client.read()); #endif - //if Tx data available, get it from C64 - if ((SwiftRegStatus & SwiftStatusTxEmpty) == 0) + //send collected Tx data to host when full, when the C64 pauses, or when the oldest byte has waited long enough + if (SwiftTxOutLen && (SwiftTxOutLen == SwiftTxOutSize || + micros() - SwiftTxOutLastMicros >= SwiftTxOutIdleMicros || + micros() - SwiftTxOutFirstMicros >= SwiftTxOutMaxMicros)) SendSwiftTxOut(); + + //if Tx data available, get it from C64 (while SwiftTxOut is full, Tx stays not empty: the C64 waits until the stack has taken some) + if ((SwiftRegStatus & SwiftStatusTxEmpty) == 0 && SwiftTxOutLen < SwiftTxOutSize) { if (client.connected() && !BrowserMode) //send Tx data to host { //Printf_dbg_sw("send %02x: %c\n", SwiftTxBuf, SwiftTxBuf); - //client.print((char)SwiftTxBuf); //send it - client.write(SwiftTxBuf); + if (SwiftTxOutLen == 0) SwiftTxOutFirstMicros = micros(); + SwiftTxOutLastMicros = micros(); + SwiftTxOut[SwiftTxOutLen++] = SwiftTxBuf; //sent by SendSwiftTxOut() //client.flush(); //adds ~100mS! if(SwiftTxBuf=='+') { From d42086f8c64701b402362bd18464dbc3c69cb562 Mon Sep 17 00:00:00 2001 From: Wes Biggs Date: Mon, 2 Nov 2026 00:47:23 -0600 Subject: [PATCH 2/2] SwiftLink Tx: allocate SwiftTxOut with the other buffers, clear it on a connection change, send at once when nothing is in flight Review follow-ups: - SwiftTxOut is malloc'd in InitHndlr_SwiftLink() and freed in FreeSwiftlinkBuffs(), so other modes keep the 256 B of RAM2 - emptied in the connection-change block, so Tx bytes for an old connection cannot reach a new one - sent right away when nothing is unacknowledged (availableForWrite() >= FNET_SOCKET_DEFAULT_SIZE, Nagle's rule) Co-Authored-By: Claude Opus 5.5 --- .../MinimalBoot/Common/IO_Handlers/IOH_Swiftlink.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_Swiftlink.c b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_Swiftlink.c index 6f80b525..99b397fc 100644 --- a/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_Swiftlink.c +++ b/Source/Teensy/MinimalBoot/Common/IO_Handlers/IOH_Swiftlink.c @@ -182,10 +182,11 @@ uint32_t C64CycBetweenRx, LastTxMillis = millis(); //Tx bytes from the C64 are collected here and sent with one client.write() per burst (one TCP segment per byte before), and only as // much at a time as the stack has room for: client.write() busy-waits for send buffer space, which stalls the main loop until the // stack frees some (and for good when the stack is polled from the main loop). While this buffer is full the C64 sees Tx not empty. +// Sent right away when nothing is in flight (Nagle's rule), otherwise collected until one of the limits below. #define SwiftTxOutSize 256 #define SwiftTxOutIdleMicros 2000 //send once the C64 has sent nothing for this long, #define SwiftTxOutMaxMicros 20000 // or this long after the oldest byte waiting -DMAMEM uint8_t SwiftTxOut[SwiftTxOutSize]; +uint8_t* SwiftTxOut = NULL; //allocated in InitHndlr_SwiftLink() uint16_t SwiftTxOutLen = 0; uint32_t SwiftTxOutFirstMicros, SwiftTxOutLastMicros; @@ -287,6 +288,7 @@ void FreeSwiftlinkBuffs() for(uint8_t cnt=0; cnt"); //add special tag to catch when complete @@ -602,10 +607,12 @@ void PollingHndlr_SwiftLink() while (client.available()) AddRawCharToRxQueue(client.read()); #endif - //send collected Tx data to host when full, when the C64 pauses, or when the oldest byte has waited long enough + //send collected Tx data to host when full, when the C64 pauses, when the oldest byte has waited long enough, + // or when nothing sent earlier is still unacknowledged (FNET_SOCKET_DEFAULT_SIZE: the socket size, never changed with Ethernet.setSocketSize()) if (SwiftTxOutLen && (SwiftTxOutLen == SwiftTxOutSize || micros() - SwiftTxOutLastMicros >= SwiftTxOutIdleMicros || - micros() - SwiftTxOutFirstMicros >= SwiftTxOutMaxMicros)) SendSwiftTxOut(); + micros() - SwiftTxOutFirstMicros >= SwiftTxOutMaxMicros || + client.availableForWrite() >= FNET_SOCKET_DEFAULT_SIZE)) SendSwiftTxOut(); //if Tx data available, get it from C64 (while SwiftTxOut is full, Tx stays not empty: the C64 waits until the stack has taken some) if ((SwiftRegStatus & SwiftStatusTxEmpty) == 0 && SwiftTxOutLen < SwiftTxOutSize)