Skip to content

Dial-up Modem device#1523

Open
chschnell wants to merge 12 commits intocopy:masterfrom
chschnell:modem
Open

Dial-up Modem device#1523
chschnell wants to merge 12 commits intocopy:masterfrom
chschnell:modem

Conversation

@chschnell
Copy link
Copy Markdown
Contributor

Addresses #1512.

Add a serial Modem device, see the included documentation for a user guide.

With a few exceptions, this device follows ITU Recommendation V.250 in regards to the AT command interpreter, AT command syntax and the chosen subset of AT commands.

The UART class had to be extended to emit bus messages for changes of its DTR and RTS hardware signals.

There is a slight ugliness in the Modem constructor: It needs to use the bus to send configuration messages to the UART device, but the bus/UART isn't ready at that point in time, so this is delayed until bus event "emulator-ready" is received. It works reliably, but is there a better pattern for this, perhaps move the Modem construction down in the v86 boot sequence to a later point in time, after the CPU has initialized? Or add and call a method Modem.initialize() that is called after CPU initialization?

The only open issue left is what to store in v86 state snapshots for this device, I thinks this needs to be talked over. For now I've left out Modem.get_state() and set_state() entirely.

Added bus messages "serial<N>-data-terminal-ready-output" and "serial<N>-request-to-send-output" for RS-232 hardware output signals DTR and RTS, respectively.
- added new files "modem.js" and its documentation "modem.md"
- added new debug log type LOG_MODEM
- integrated Modem device into V86 options and web interface
Comment thread src/browser/main.js
Comment thread src/browser/main.js Outdated
Comment thread src/browser/modem.js Outdated
Comment thread src/browser/modem.js Outdated
Comment thread src/browser/modem.js Outdated
Comment thread docs/modem.md
* [TELIT] [AT Commands Reference Guide](https://docs.rs-online.com/c5f6/0900766b81541066.pdf)
* [websocketd](http://websocketd.com/), also [websockify](https://linux.die.net/man/1/websockify)
* [WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
* [WebRTC API](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API)
Copy link
Copy Markdown
Contributor

@SuperMaxusa SuperMaxusa Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is WebRTC mentioned/used anywhere?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is WebRTC mentioned/used anywhere?

That's the only place it's mentioned, it's not used anywhere, and here's why I mentioned it:

The WebSocket backend implementation I'm currently using for the Modem only allows outbound calls, but Modems can make outbound calls and also accept inbound calls (therefore, the whole RING/ATA-mechanics of the Modem are currently dead).

WebRTC, on the other hand, supports both in- and outbound "calls" (connections), but needs a signalling server and protocol to signal things like for example INVITE/BUSY/ACCEPT and to penetrate firewalls using TURN/STUN/ICE (or whatever).

WebRTC supports binary end-to-end data channels, but I couldn't find an easy way to set this up (like I was able to with the general-purpose WebSocket server websocketd), and the implementation in Javascript looks like a piece of work. I lack practical experience with WebRTC, so I didn't pursue this any further, but I wanted to mention it to not forget about it :)

@copy
Copy link
Copy Markdown
Owner

copy commented Apr 20, 2026

Thanks! This looks very cool and I'd like to merged it.

I've given a basic review, but due to unfamiliarity with modems I can't review it detail. That's fine, it makes you the official maintainer of the modem code :-)

There is a slight ugliness in the Modem constructor: It needs to use the bus to send configuration messages to the UART device, but the bus/UART isn't ready at that point in time, so this is delayed until bus event "emulator-ready" is received. It works reliably, but is there a better pattern for this, perhaps move the Modem construction down in the v86 boot sequence to a later point in time, after the CPU has initialized? Or add and call a method Modem.initialize() that is called after CPU initialization?

I agree, in the future I would like to remove internal-only bus events and use direct method calls (e.g. modem receives an uart object or vice-versa). Adding an initialise function sounds fine to me. Or move the modem to cpu.js and pass the uart object to it (that also makes the state handling easier).

I tested some basic commands and connecting to a websocket server. Is there a way to test internet connectivity with user-mode-style networking (e.g. slirp, passt or our own code)?

Comment thread docs/modem.md
New uart number scheme:
- 0: disable Modem (no UART)
- 1: use UART0 (COM1)
- 2: use UART1 (COM2)
- 3: use UART2 (COM3)
- 4: use UART3 (COM4)

This new scheme is used in the web UI and in the modem configuration, only the Modem class maps the uart number x to UARTx-1.

In starter.js, a Modem object is instantiated only if the uart number is not 0 (or undefined).

Also:
- added query parameter for modem (uart number) in main.js
- removed UART0 from web UI (reserved for serial connection) in index.html and debug.html
In addtion to the strict zero-padded IP/Port-number number notation, added support for dotted IPv4-notation with arbitrary separator characters (i.e. "1.2.3.4:5678" may also be written as "1-2-3-4-5678"). Any non-digit and non-whitespace characters may be used as separator characters.

Added range checks for all numbers (0-255 for IPv4 octets, 0-65535 for Port numbers).

Updated dial string documentation in modem.md.
@chschnell
Copy link
Copy Markdown
Contributor Author

Thanks! This looks very cool and I'd like to merged it.

I've given a basic review, but due to unfamiliarity with modems I can't review it detail. That's fine, it makes you the official maintainer of the modem code :-)

Great, and that's perfectly fine with me! When I was a teenager I went around, told people what kind of Modem to buy and then installed Trumpet Winsock on their PCs to show them the Internet. Modems and I go way back (as well as BBSes, MUDs and such). I'm still using Modems at work, they now have SIM cards but the basics never changed :)

There is a slight ugliness in the Modem constructor: It needs to use the bus to send configuration messages to the UART device, but the bus/UART isn't ready at that point in time, so this is delayed until bus event "emulator-ready" is received. It works reliably, but is there a better pattern for this, perhaps move the Modem construction down in the v86 boot sequence to a later point in time, after the CPU has initialized? Or add and call a method Modem.initialize() that is called after CPU initialization?

I agree, in the future I would like to remove internal-only bus events and use direct method calls (e.g. modem receives an uart object or vice-versa). Adding an initialise function sounds fine to me. Or move the modem to cpu.js and pass the uart object to it (that also makes the state handling easier).

I've also thought about using direct calls instead of the bus here. But I've left the code as it is now, this is a larger topic.

I tested some basic commands and connecting to a websocket server. Is there a way to test internet connectivity with user-mode-style networking (e.g. slirp, passt or our own code)?

At the bottom of the Modem manual "docs/modem.md" you find two example websocket servers for the Modem, the second one is a complete PPP server (PPP is much superior to SLIP and CSLIP) which provides an IP address pool for clients and gives them full internet access (PPP clients can also directly access each other, so one PPP client can run an IP server and another PPP client can connect to it).

If you or anybody else wants, download my Win3.11 image win311-128m.zip (32 MB) with everything preinstalled, here is how you use it:

First, get the PPP server up and running (see my second example in "docs/modem.md"). Then:

  • in the v86 web UI, select the Win311 HDA image, Boch's BIOS, Boch's VGABIOS and UART1 for the Modem
  • boot, then open the "Trumpet Winsock" folder at the lower right
  • double-click "Trumpet Winsock", click through the expired license message: OK, ACCEPT then CANCEL (doesn't matter)
  • select "Manual login" from the "Dialler" menu
  • enter ATZ and press ENTER, you should see the response OK
  • connect to your PPP server using dial command ATD...
  • once you see the response CONNECT and some garbage characters, press ESC twice (once to escape the browser's mouse/keyboard lock, and once more to complete Trumpet's manual login procedure)
  • if you see the response PPP ENABLED then you're done, minimize the Trumpet Winsock window
  • open the "Netscape Communicator" folder at the lower right
  • double-click "Netscape Navigator", it should open and load its homepage (it's a German page, the first link at the top takes you to the English version)

I don't know why I need Boch's with thie image, but SeaBIOS crashes with some memory error.

Changed the response to command lines without any AT command from ERROR to OK (this might be an empty line or some garbage characters).

ITU-T V.250 states in "5.6 Executing commands": If no commands appear in the command line, the OK result code is issued.

Trumpet Winsock's auto-dial script tripped over this, I didn't notice this earlier.
Comment thread src/browser/main.js Outdated
settings.disable_audio = bool_arg(query_args.get("mute"));
if(query_args.has("modem"))
{
settings.modem = { modem_uart: parseInt(query_args.get("modem"), 10) || 0 };
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modem_uart -> uart

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah thanks!

There was another little issue with this, see commit 7826553.

@chschnell
Copy link
Copy Markdown
Contributor Author

I'm trying to get the Trumpet Winsock auto-dialler to work properly, which it currently doesn't.

During debugging I've found two problems:

  1. I need to change the Modem's response behaviour to AT command lines in three edge cases:
    1.1 when the command line is empty
    1.2 when the command line is not empty but doesn't contain any AT command
    1.3 when the command line buffer has overflown and we receive a line terminating character (CR)

  2. The Trumpet Winsock auto-dialler does not detect the LOW->HIGH transition of the UART's Carrier-Detect (CD) signal (at the end of a successfull dial command).

The first issue is easy, I think I know what to do there after having played around with this a bit.

But the second one is strange. I've looked at the code in src/uart.js, and I believe that it should raise an interrupt in UART.set_modem_status(status) if the Interrupt-Enable (IER) bit is set (or so, that's from the top of my head).

@copy: Is it possible that this was forgotten? This would explain why Trumpet Winsock can't detect the signal transition (and thus hangs in the auto-dialler). In case someone knows more please post, else I'll try to look up a proper source for this information later.

Comment thread src/browser/modem.js Outdated
}
}

if(DEBUG)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any purpose for this if(DEBUG)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's just a leftover from an earlier state when I had lots of debugging information there, since it isn't needed anymore I removed this condition in commit 2a93655.

Thank you!

- fixed options member name confusion (modem_uart/uart)
- added assignment of "modem" query argument to the HTML <select> element's value on page load
- changed AT command line evaluation (after receiving the line terminating CR character):
  - if the command line buffer is empty, either abort the current dial attempt with NO CARRIER response, or else return OK response
  - if the command line buffer is not empty but without AT command, do NOT send any response (else Trumpet Winsock trips over this)
  - if the command line buffer has overflown in the past, do NOT send any response (else Trumpet Winsock trips over this)
- clear data-mode send buffer after connecting to WebSocket server
- fixed &F command, it now no longer terminates commmand line evaluation
@chschnell
Copy link
Copy Markdown
Contributor Author

chschnell commented Apr 22, 2026

The first problem with the Trumpet Winsock auto-dialler is fixed, but not the second one.

It's true that the 16550A raises an interrupt for Modem Status Register changes (when it's asked to), I'm trying to add it but I'm struggeling a bit with class UART.

Since there wasn't any Modem in v86 before, I think I'm the first to use the UART's Modem Status register, this register's delta bits and the associated interrupt. A plain serial connection doesn't use any of these.

I thought I had figured out what to change in uart.js (raising the Modem Status Register interrupt under the right conditions), but the guest freezes the moment the interrupt is raised. So I looked a bit deeper into class UART, and I believe to have found several issues around this, it's a bit of a rabbit hole.

I don't remember all the 16550A details (it's been forever that I worked directly with one), so before I can get into details I'll have to check some things in the datasheet, ugh.

Not sure what I've stumbled into this time, but does it sound plausible what I'm thinking? How (well) was this tested, if I may ask? :-)

@chschnell chschnell marked this pull request as draft April 22, 2026 19:04
@copy
Copy link
Copy Markdown
Owner

copy commented Apr 22, 2026

Not sure what I've stumbled into this time, but does it sound plausible what I'm thinking? How (well) was this tested, if I may ask? :-)

Well, it was ported from jor1k and most other changes after that were quick fixes. The modem status was added in #980.

@chschnell
Copy link
Copy Markdown
Contributor Author

Not sure what I've stumbled into this time, but does it sound plausible what I'm thinking? How (well) was this tested, if I may ask? :-)

Well, it was ported from jor1k and most other changes after that were quick fixes. The modem status was added in #980.

Looks like I just fixed it, the auto-dialler is now working without any issues (yeah!). I'll post more about this tomorrow.

Please see the comment in method UART.set_modem_status_bit() for details
about the MSR.

1. Fixed register read access to MSR, we must return the unmodified MSR
   value to the PC before clearing its delta bits.

2. Removed register write access to MSR (MSR is a read-only register).

3. Removed UART bus event handler for "serial<N>-modem-status-input(data)" and
   its accompanying API method V86.serial_set_modem_status(serial, status).
   Without detailled documentation about the MSR it's too easy to derail the
   internal state of class UART with this level of access. Instead use these
   methods to modify the MSR:
       V86.serial_set_carrier_detect(serial, status)
       V86.serial_set_ring_indicator(serial, status)
       V86.serial_set_data_set_ready(serial, status)
       V86.serial_set_clear_to_send(serial, status)
   I checked and couldn't find the bus event or V86 method used anywhere
   in v86.

4. Replaced UART.set_modem_status(status) with UART.set_modem_status_bit(msr_bit, set_bit)
   This simplified (also fixed?) implementation of UART bus event handlers:
       serial<N>-carrier-detect-input(data)
       serial<N>-ring-indicator-input(data)
       serial<N>-data-set-ready-input(data)
       serial<N>-clear-to-send-input(data)

5. Added MSR interrupt management, when interrupts are enabled: raise when
   any MSR delta bit is set, lower after MSR has been read.

6. Normalized source code formatting of a few methods.
@chschnell chschnell marked this pull request as ready for review April 23, 2026 17:40
@chschnell
Copy link
Copy Markdown
Contributor Author

chschnell commented Apr 23, 2026

The modem status was added in #980.

That BBS mentioned there must have not used interrupts for the MSR and must have ignored the MSR delta bits in order to work. That's very, very old-school.

I've revised the UART's Modem Status Register (MSR) implementation, please see commit 44b9e08 for details.

I hope the third point in the commit comment is Ok (my goal is to simplify things), I think the UART method that I removed only complicates things unneccesarily. I'm aware that this might potentionally break other v86 applications in the field, but there's a clear path which methods to use instead.

Trumpet Winsock turned out to be a good sparring partner for the Modem, getting that auto-dialler to work lead to a bunch of fixes. Here's a short video of the basically unmodifed Trumpet Winsock auto-dialler (only minor things like the COM port were configured previously):

win311-trumpet-winsock.mp4

I've re-uploaded the Win3.11 image file here: win311-128m.zip, there were a few leftover misconfigurations with Trumpet Winsock. This is the image I've used in the video (it's so much simpler to use than Trumpet's Manual login procedure from above).

I think this PR is good to go now!

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.

3 participants