Skip to content

Fix two freezes in the socket CharIo backend (rcv_char_available throttle, blocking rcv_char) - #249

Open
BuilderGuy1 wants to merge 2 commits into
dingusdev:masterfrom
BuilderGuy1:fix/serial-backend-freezes
Open

BuilderGuy1 wants to merge 2 commits into
dingusdev:masterfrom
BuilderGuy1:fix/serial-backend-freezes

Conversation

@BuilderGuy1

Copy link
Copy Markdown

Fixes #248 — see that issue for the full bug report, repro recipe, and validation matrix.

Two minimal fixes to devices/serial/chario.cpp (CharIoSocket):

  1. rcv_char_available() reported "no character available" for ~785 of every 800 polls during
    dense RX.
    Any guest polling RR0_RX_CHARACTER_AVAILABLE with interrupts masked (the Copland
    kernel's LLNubDriver_WaitForByte does exactly this) parks forever and the machine wedges.
    The kernel socket buffer already prevents overrun; availability is now the select() truth.

  2. rcv_char() called a blocking recv() with no availability check. Any guest read of the
    ESCC data register while the peer-side had sent nothing blocked the emulator's CPU thread
    forever — the whole machine froze. Now MSG_DONTWAIT: EAGAIN = no byte pending; the
    rx-available bit stays the sole truth the guest polls.

Both fixes validated on the Copland D11E4 guest: the workload that wedged every prior boot now runs
to completion with the machine interactive throughout; silent-peer boots that froze at the splash now
proceed normally.

Note for reviewers: the consecutivechars reset branch was dead code because writefds = readfds
puts the listen socket in the write set, so select() always returned > 0. Left as-is in this
minimal fix; a follow-up cleanup (readfds-only select, don't recv the listen socket, small real RX
FIFO) could be a separate PR.

Jason added 2 commits October 29, 2026 14:41
…s the select() truth

The A04-22 wedge root-caused: CharIoSocket::rcv_char_available() returned 'no
character available' for ~785 of every 800 polls whenever the peer's reply
traffic was dense (consecutivechars >= 15), while the guest's
NubDriver_WaitForByte spins with MSR[EE]=0 waiting for RR0_RX_CHARACTER_
AVAILABLE (the contradictory flag=44/busy=00 state the wait-clock study
measured). The nub's debug transaction never sees its reply, the machine parks,
the uncollected ADB packets are the visible wedge (the Cuda starvation flood,
the dead cursor, the roll-15 'post-exit wedge', boot 8's repro at emu 250.4 s).

The socket buffer already prevents overrun (the kernel holds unread bytes;
select() reports them when the guest is ready); the throttle protected nothing.
The wedge's instrument-facing consequences die with it: no Cuda flood, no
NubDriver_WaitForByte park, no dead cursor.
…IT in rcv_char

A blocking recv(acceptfd) froze the whole emulated machine whenever guest code
read the ESCC data register while the peer-side debugger had sent nothing
(the silent-peer boots froze at the splash logo, CPU pegged). EAGAIN now means
'no byte pending' and the rx-available bit stays the sole truth the guest polls.
@dingusdev

Copy link
Copy Markdown
Owner

Thank you for interest in contributing to DingusPPC. Unfortunately, this commit appears to heavily use AI, which I cannot accept. If you would like to commit this, please do a version that does not use AI.

@joevt

joevt commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Regarding AI, I don't mind that it's used to fix problems. It's just the AI-speak that bothers me.


rcv_char_available() reported "no character available" for ~785 of every 800 polls during

This is intended. It's a workaround for a problem in Open Firmware where it can only accept 15 out of ~800 serial port receive polls at a time. I have commits in my fork to change this so it can be disabled:

  • chario: Move text pacing feature to base class.
  • escc: Add text_pacing option and default to off. (I need to add a fix for this one to allow it to enable text pacing).

Something strange though: With --text_pacing set to 0 (or off), I get an illegal instruction in Open Firmware when trying to paste more than 16 characters (using B&W G3 ROM). I hope this is just a bug in Open Firmware (on top of the fact that Open Firmware is polling the serial port when it can't accept additional characters).


rcv_char() called a blocking recv() with no availability check.

Not sure about this one. recv is called by rcv_char which is only called by EsccChannel::receive_byte when rcv_char_available_now returns true. Can you describe how recv can be called when there's nothing to receive?

Certainly, using MSG_DONTWAIT is preferable in either case to ensure no blocking occurs.


Note for reviewers: the consecutivechars reset branch was dead code because writefds = readfds
puts the listen socket in the write set, so select() always returned > 0.

select can return 0 when acceptfd is still -1. You're right that it won't return 0 when acceptfd is not -1 and writefds = readfds.

I'll add a fix to reset consecutivechars if the acceptfd has nothing to read.


I don't see a problem with these commits for the main fork (unless you care about pasting text into Open Firmware without the dl command).

I can rebase my fork with changes to restore the text_pacing feature.


Serial port DMA requires additional changes in my fork since I removed polling from the DBDMA emulation. I need to add a thread for reading from serial. Hopefully after that I'll be able to connect two instances of DingusPPC where one is running Copland and the other is running the PowerPC debugger.

@BuilderGuy1

Copy link
Copy Markdown
Author

I would like to start by apologizing for submitting AI output in your project. I wasn't sure how well it would be accepted and thought posting it was a bit risky. I decided to post it because I had done 2 days of testing with the AI, the conclusions seemed reasonable and, hopefully, useful to the project.
Thank you for investigating the claims and I look forward to your improvements !
My project (yes, AI driven..) would not be possible without DingusPPC ! Having a debugger and a virtual PowerMac makes the project feasible to even try doing (I'm a hobbyist trying to produce something for the community).
Is there a better forum/space where I could pose "questions" rather than doing PRs?
Again, thank you for your time on the claims and on DingusPPC :-)

@BuilderGuy1

Copy link
Copy Markdown
Author

Found the Discord and the E-Maculation subforum; I'll ask questions there rather than opening PRs. Thanks again.

@joevt

joevt commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Something strange though: With --text_pacing set to 0 (or off), I get an illegal instruction in Open Firmware when trying to paste more than 16 characters (using B&W G3 ROM).

In Open Firmware, read-ahead calls scc-read with a buffer length of 4 but the buffer gets overrun because scc-read only checks the buffer length once.

035: b(code) \ [0x0bf] 0x49e scc-read                       ;   int scc-read(uint8_t* buf, int len, uint8_t* reg)
                                                            ;   {
048: 7E88 A378                       mr       r8,r20        ;                                                           // reg = scc registers base
04C: 807F 0000                       lwz      r3,0(r31)     ;                                                           // len = size of buffer
050: 849F 0004                       lwzu     r4,4(r31)     ;                                                           // buf = buffer
054: 3BFF 0004                       addi     r31,r31,4     ;                                                           // pop
058: 7C63 1B79                       mr.      r3,r3         ;       status = len;
05C: 3A80 0000                       li       r20,0         ;       index = 0;
060: 4182 0020       begin:          beq      done          ;       while (status) {
064: 8808 0000                       lbz      r0,0(r8)      ;                                                           // status = reg[0];
068: 7000 0001                       andi.    r0,r0,1       ;           status = reg[0] & RR0_RX_CHARACTER_AVAILABLE;
06C: 4182 0010                       beq      continue      ;           if (status) {
070: 8808 0010                       lbz      r0,16(r8)     ;                                                           // read scc data
074: 7C04 A1AE                       stbx     r0,r4,r20     ;                                                           // buf[index = data                 
078: 3A94 0001                       addi     r20,r20,1     ;               buf[index++] = reg[0x10];
                                                                        }
07C: 4280 FFE4       continue:       b        begin         ;       }
                                                                return index;
080: 4E80 0020       done:           blr                    ;   }
084: 0000 0000                                         

read-ahead assumes that scc-read won't exceed the buffer length. Given that scc-read doesn't check the buffer length, the assumption can only be true if the SCC receive buffer is 4 characters or less (am85c30 has a 3 byte receive buffer?) and that scc-read can read 4 characters from the receive buffer faster than a fifth character can be added to the receive buffer.

The assumption may be true for real hardware but is currently not true for DingusPPC.

The current text pacing workaround in DingusPPC allows for 14 consecutive characters. This probably should be changed to 4 characters to match the above problem description. In the Open Firmware dictionary, there are 8 bytes between the start of the 4chars variable (FF845350) and the header of the next dictionary word. Reading more than 8 characters would corrupt the Open Firmware dictionary. Reading more than 0x28 would corrupt code in the Open Firmware dictionary.

FF84533C: 80                                                    
FF84533D: b(variable) \ [0x0b9] 0xbed 4chars
FF845348: 7E48 02A6                       mflr     r18          
FF84534C: 4BFC 3F0D     FF809258          bl       (var)        
FF845350: 0D00 0000                       dc.l     0xD000000    
FF845354: 0000 0000                                             
FF845358: FFFF FFE0     FF845338                                
FF84535C: 80                                                    
FF84535D: b(buffer:) \ [0x0bd] 0xbee read-ahead-buffer
FF845378: 7E48 02A6                       mflr     r18          
FF84537C: 4BFC 3DD5     FF809150          bl       (val)        
FF845380: FF8D AB80                       dc.l     0xFF8DAB80   
FF845384: 0000 0000                                             
FF845388: FFFF FFD0     FF845358                                

I tried pasting more than 16 characters with --text_pacing=1 but the max I've seen get added to 4chars is 8.
Actually, that makes sense, since consecutive_chars is incremented twice per character - once for reading the status register and again for reading the data register. Indeed, changing chars_consecutive_max from 16 to 17 causes Open Firmware dictionary corruption when pasting more than 8 characters.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Socket serial backend freezes guests under debug-channel load (3 bugs + fixes)

3 participants