I have been using Laplink Pro 4.0a to transfer files between a modern Windows 10 desktop PC (via DOSBox) and several vintage DOS computers. Using a standard null-modem serial cable, in my testing it seems to achieve a very reliable 80-90Kb/s (for comparison's sake, let's call that "in the realm of 85,000 bps") with compression off, between any two computers. This includes a 4.77MHz 8088 IBM PC using the original IBM ASYNC serial adapter with an 8250 UART, which is generally considered unreliable/unusable above 9600 bps, due to a 1 byte FIFO buffer and the need for the CPU to service a hardware interrupt for every byte received. The IBM Technical Manual for the ASYNC card also states that it "allows operation from 50 baud to 9600 baud".

It has as an "Accelerated (7-wire)" mode in addition to "Standard (3-wire)" mode, though there's little documentation for how these actually function. I could speculate that if the programmers were particularly clever they could use some of the additional signaling pins (DTR, DCD, DSR, etc) to transmit additional bits to achieve a somewhat parallel type of transfer. There is also a "Turbo Mode", which is not streaming compression, as that's separate option that can be enabled/disabled as well. I would be very interested in to know what some of those actually do.

All that to say, how did Laplink manage this form of dark magic to achieve transfer speeds almost 10x what is generally considered possible on such hardware?

  • 12
    The UART could be driven through polling if you didn't need to do anything else. Polling avoids the overhead of interrupts. Jul 23 at 14:20
  • 11
    The great thing about DOS was you could take direct control of the hardware. If the program (Laplink) is just receiving data on the serial port and shoving it into a buffer (occasionally dumping to a file while pausing the download), you can have it do just that, bypassing the interrupts.
    – Jon Custer
    Jul 23 at 14:34
  • 11
    Considering that the 1MHz 6502 in the Apple ][ manages 115Kbps with a 6551 UART (which also has a one-byte buffer), this seems entirely possible with a tight polling loop on a 4.77MHz 8088.
    – Brian H
    Jul 23 at 14:59
  • 2
    Mind to clarify what numbers you're talking about? From context it seems as if the mentiond Kb/s are kBit/s or are they KiBit/s? Or ratehr KiByte?
    – Raffzahn
    Jul 23 at 15:54
  • 3
    The Laplink program doesn't give any actual rate stats, or even an elapsed time for transfer, so I'm basing this on a stopwatch and then calculating rate using a simple online calculator. I used two test files, one a 7MB ZIP file, and the other a 1.25MB floppy disk image, both with the streaming compression turned off so the contents wouldn't matter. The time for the 7MB file (I tested it twice in both directions) was approx 10 mins 30 seconds, and the 1.25MB file was almost 2 minutes exactly. So I meant 80-90 Kilobits/second.
    – 640KB
    Jul 23 at 16:36

It's not dark magic, it's just that there is no hardware limit for 9600 bps to begin with.

There are many factors at play here, it's just not about the UART chip.

The speed depends on basically from these items:

  • Design & marketing plans/specs of an IBM PC before hardware was designed
  • CPU speed
  • ISA bus speed between CPU and UART
  • Disk transfer speed
  • The UART chip itself
  • How the UART chip is clocked
  • The level translation circuit to convert between TTL and RS-232 voltage levels
  • The RS-232-C standard itself
  • Operating system (DOS)
  • Software (such as Laplink or Fastlynx)
  • Null modem cable used

The Wikipedia page does not tell the whole truth - yes of course if you use interrupts, and there is the weird assumption of 1ms interrupt latency, it will of course result into a limit of 1000 bytes per second, which gets approximated to standard rate of 9600 bps. If you don't have such latency, or don't use interrupts to begin with, you can send at any rate the hardware is otherwise capable of. There is no explanation why would a system have this 1ms interrupt latency, as it makes no sense, at least in the case of the IBM PC. Any of those UARTs listed could most likely go past 9600 bps just fine.

And regarding the IBM PC specifically, it is likely that general specs were first fixed before actually it was designed. If the specs say that it must have serial connectivity up to 9600 bps, then that's the specs, even if designers later on chose an UART chip that is capable of faster speeds.

CPU speed and ISA bus speed are obviously not issues. Even the 4.77 MHz 8088 can talk via ISA bus to the UART chip at a rate of about 0.9 megabytes per second. Good enough for transferring at 115200 bps even.

The largest effect comes via disk transfer speed. Most likely data transfer between UART and disk are interleaved, as you don't want to miss UART data byte interrupts while handling disk interrupts. So there has got to be a small delay when accessing disk before full rate UART transmission can continue.

The UART chip that IBM first used was National INS8250N-B. It can work with up to 3.1 MHz baud generator clock, but there are limitations in the divided frequency. Divide by 1 must be below 1 MHz, divide by 2 must be below 2 MHz, and divide by 3 must be below 3 MHz. The datasheet gives example baud rate tables for two different clock values, 1.8432MHz and 3.072MHz. Since both are high enough, and slower clock is always easier to deal with, IBM chose the same 1.8432 MHz for their baud clock crystal. Either way, divisor of 1 will exceed the safe chip baud clock limits, so basically, 115200 bps should not be used. Divisor of 2 is safe to use regarding the divided clock limit and it gives 57600 bps. However, the datasheet mentions that baud rate should not exceed 56 Kbaud, which means first usable safe divisor is 3 equaling 38400 bps - far beyond intended use anyway.

Then, there are the RS-232 tranceivers which convert the signals between TTL level UART and RS-232 connector voltage levels. They are SN75150 transmitter and SN75154 receiver. Datasheet for SN75150 says that it can drive a fully RS-232 compliant load when used at up to 20 Kbps. So under worst case load, you are limited to 20 Kbps, while with lighter loads it is possible to go faster than 20 Kbps in theory.

But why the tranceivers are specified only up to 20 Kbps? It comes from the RS-232 standard - it defines an interface with electrical specifications up to 20 Kbps. So even if you used faster speeds, they would not be governed by the specs - if it works then it works, but it does not have to work, it's beyond the standard so it does not apply.

Since marketing specs only go up to 9600 bps, so does the BIOS support. And OS mainly used the BIOS, so even if later DOS versions could maybe go up to 19200 bps, the early DOS versions were limited to 9600 bps.

Any software running on the DOS system can do whatever it likes, whether or not the hardware actually supports it.

So, in light of this, everything in the hardware should be specified to work to 19200 bps in RS-232 standard compliant fashion. If the tranceivers and wiring allows, going above the specs to 38400 bps is not an issue either since the UART supports it. 57600 bps will violate the 8250 UART datasheet statement about max baud rate of 56 Kbps, but if it works it works. 115200 bps is kind of pushing it, as it violates the baud rate clock limit, in addition to the all previous limitations of the hardware, but given a right UART type or if it just happens to works, it works.

Sure, the later 8250A and clone chips might fare better than the original 8250.

But given the right hardware, there should be no problems configuring UART for 115200 baud settings, and transferring 11520 bytes per second. Which equates to 92160 actual payload data bits per second. So yes, 90 kbps sounds doable, even on a 4.77 MHz 8088. The 9600 bps baud rate is an arbitrary limit which does not exist in any way.

Some data transfer programs such as Laplink and Fastlynx also use the UART handshake wires for data transmission, which allows to transfer even faster than the selected baud rate allows. Typically, only DTR->DSR and RTS->CTS wiring is used for extra speed, and this is the so-called 7-wire null modem cable. The 3-wire null-modem cable only used the TXD and RXD with ground for the data transfer.

Turbo Mode, if memory serves, means larger data packets over the UART is transmitted to reduce the overhead of the headers. It also means the computer spends larger time periods doing nothing except transferring the data, so it won't respond to timer and other interrupts. This may cause incompatibilities and so this method does not always work on all computers or background TSRs.

The cables are recommended to be less than 15m (50ft) as per the standard, but longer cables are allowed as long as they don't exceed the cable capacitance limit of 2500pF, and that the capacitance is low enough so it does not slow down the signal rise/fall times beyond the limits (4% of bit length).

  • 1
    As I recall, one of the chief design limitations on serial port speed was the length of the cable. Standard RS-232 allowed for a pretty long cable as I recall, and the higher speeds required a shorter cable. Jul 23 at 22:58
  • 3
    Reasonable lengths (<5m) weren' really a problem, even at 115k2
    – tofro
    Jul 24 at 12:52
  • This is a great answer. I didn't realize (or completely forgot) that there were different handshaking wirings for null modem cables. The cable I have been using is a partial handshaking type and I've ordered a full handshaking cable to compare. Would make sense that I wouldn't be seeing closer to line speed rates using only software handshaking. Also makes sense that "Turbo Mode" would use larger or sliding windows similar to Zmodem or Ymodem-G. Though I still can't seem to locate any LLPro 4 docs that mention any of these even at a "marketing level".
    – 640KB
    Jul 24 at 15:06
  • 2
    @640KB The WD8250 is a clone, but it still repeats the same mantra - specified up to 56k, divisor of 1 not allowed (to get 115200 bps) and in no case should the baud rate exceed 56k. Then they say please contact sales how to get faster baud rates. Well, it still seems to work, even if it is not guaranteed. Oh and while the handshake lines are separate wires and called hardware flow control, they are not handled in hardware. Sure, they may generate interrupts if you want them to, but it's the software that must read and write the handshake pins and work based on those signals.
    – Justme
    Jul 24 at 20:21
  • 2
    That 1ms interrupt latency thing is really bizarre to me. I did a lot of serial comm stuff (including programming) back in the day and I'm thinking it's a misinterpretation as you could easily run an 8250 at 57.6 kbps. 115.2 kbps is a bit rough at 4.77 mhz whatever chip you use (for ones that support it) as available cycles are getting pretty tight, but if you don't have too much hooked into the timer interrupt it can work. I think what it probably means to say is that if you used the system (software) interrupts to do comm, you were going to max out at 9600 bps at 4.77 mhz... Jul 25 at 21:58

A tight polling loop can easily achieve a solid transfer speed of 11,520 bytes/second over a 115,200 baud serial link, even when using a moderately slow processor. Even if a processor can only run 250,000 instructions/second (which is pretty slow), it could afford to spend more than 20 instructions dealing with each byte. Disabling interrupts would likely be necessary, since even a minimal interrupt handler could easily cause execution to get waylaid for more than the time between bytes, but direct-polled I/O routines that don't have to do anything else while they service a single device can run much faster than interrupt-driven routines or routines that need to interleave I/O with other processing.

By way of comparison, the Apple Disk II floppy controller transfers data at a rate more than twice as fast as a 115,200-baud serial port, and with even tighter timing requirements. On a CPU where reading an I/O register takes four cycles and a branch takes three, reading a disk requires that each byte of data be read within a 7-cycle polling window which may start as soon as 30 cycles after the previous byte was received [it's not coincidence that the time required for a read and branch total 7 cycles]. Writing a disk requires that bytes be fed to the controller at precise 32 cycle intervals. These timing requirements are far stricter than required when using a typical UART at 115,200 baud, and yet Apple Disk Controller II wouldn't work at all if they weren't satisfied.

For various reasons, it's necessary to split I/O operations into chunks, between which the CPU isn't stuck in a really tight loop. A file-transfer program may be able to receive a chunk of data into memory at 11,520 bytes/second but would then need to both write it to disk and check whether a keystroke has been hit. Disk I/O on the Apple may read a sector using a very tight read routine, but would then typically have to spend some time decoding the data(*) and deciding what to do next.

(*) For various technical reasons, only about 80 different 8-bit data patterns can be stored reliably on a floppy, so most programs that read/write disks take each group of 256 bytes and write them out using 343 encoded bytes on disk. Most disk-reading code on the Apple II reads out 343 raw bytes from the disk and then decodes them once they've all been read, but it's possible to write disks in such a fashion as to allow on-the-fly decoding.

  • 1
    I'd assume a good implementation would have the sending side reading a new block into memory while the receive side is writing the previous block to disk, so serial port utilization can be maximized. Jul 26 at 0:54
  • 1
    The sender wouldn't have anything better to do once it's finished a block of data, and the receiver wouldn't have anything else it could do at that point. Receiver would probably start sending "ready for next block" signals once it's ready for more data, since writing is usually slower than reading, and sender would reply once it has more data. It might be a good idea for software to pause briefly after every 512 characters and have the receiver indicate when it's ready for more (without bothering to write to disk until it has received many blocks)...
    – supercat
    Jul 26 at 14:53
  • ...so as to allow the transmitter to briefly enable the timer-tick interrupt briefly every 50ms. The cost of doing this could be minimized by having the system encode a 16-bit CRC for each packet in three bytes, the first two of which have the high bit clear and the last of which has the high bit set, and after each packet having the sender blindly send the first two of those bytes before for the next packet before it receives the go-ahead. If the receiver happens to receive those bytes correctly, it could tell the sender, which could then omit two bytes of the CRC at the end of the packet.
    – supercat
    Jul 26 at 14:59
  • My experience based on watching the disk activity lights is that the receive side is buffering the incoming data to memory and then flushing to disk when memory is nearly full or the transfer is complete. The program says it requires something like 512k of free RAM to run even on it's lowest configuration so I'd imagine a good part of that is dedicated to receive. Even at max speed you're still at close to a minute for half a MB, so pausing the incoming data once a minute is probably a decent trade off. EMS is supported where available so you could have a pretty good buffer before writing.
    – 640KB
    Aug 4 at 12:14
  • Also, Laplink appears to use a fairly large block size (16KB or 32KB perhaps). This is based on my observation when there is a transfer error that it seems to pause and retry at least that much. I'd guess the idea is that if you are using a direct null modem cable, your error rate should be pretty close to none so retries should rarely be needed.
    – 640KB
    Aug 4 at 13:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.