Sometimes we want to connect a serial terminal -- think a laptop pretending to be a DEC VT100 -- to a machine running Linux.
Note that this is the same technology as used for a serial console, but the intent is very different. A serial console is a serial terminal which sees the console messages and can have elevated privileges. A machine can only have one console. Because console messages can't be buffered for long, the serial console does not typically run flow control or obey modem status lines. All these factors make ports configured as serial consoles unsatisfactory for use as a general-purpose serial terminal.
This posting is about creating an effective serial terminal. As might have existed on a UNIX minicomputer of twenty years ago.
Get the cabling correct. If you get the status signals wrong then if you forget to logout you (or worse still, someone else) can connect back into an old running session.
To interconnect two IBM PC/AT DTE connectors (as you might find on the rear of a desktop computer, on a laptop, or on a USB/RS-232 dongle) you want a RS-232 cross-over cable wired like this:
DCD (in) 1 ---+-------------------- 4 DTR (out) DSR (in) 6 ---+ RxD (in) 2 ------------------------ 3 TxD (out) TxD (out) 3 ------------------------ 2 RxD (in) DTR (out) 4 --------------------+--- 1 DCD (in) +--- 6 DSR (in) Gnd 5 ------------------------ 5 Gnd RTS (out) 7 ------------------------ 8 CTS (in) CTS (in) 8 ------------------------ 7 RTS (out) RI 9 ---nc nc--- 9 RI
Connecting Gnd to Gnd, Tx to Rx, Rx to Tx is pretty obvious. Connecting the handshaking lines CTS to RTS, RTS to CTS is a little less obvious, but simple enough. The Data Terminal Ready output has to assert both the Data Set Ready input (which indicates that all the other lines have a valid signal) and the Data Carrier Detect input (which indicates that the line has a connected call). The Ring Indicator is a bit of an oddity (it's only on Intel UARTs) and as the signal follows the ring tone of an incoming call it's next to useless as a status line for anything but an acoustic coupler modem.
When we start a terminal emulator it will assert DTR and that will let the other computer know that there are valid signals (via DSR) and there is a connected call (via DCD).
When we use the terminal emulator TxD and RxD will exchange data. If there is too much data to be buffered then the host will deassert RTS, this will be see as a deasserted CTS at the sender, and it will stop sending until CTS is asserted once more. This flow control allows a huge cut-and-paste without loss of data, which is something you don't miss until it doesn't work.
When we exit the terminal emulator it will drop DTR, that will drop DSR and DCD at the host, and any logged-in session will be cleared down. If you restart the terminal emulator you will see another login prompt, not an in-progress session.
If you make the cabling yourself, then save yourself some pain and buy some pin-type RS-232 backshells and a pin crimper rather than the solder-type RS-232 backshells. You should ideally use shielded cable, but UTP will work fine for a few metres.
See this previous posting about using RS-232/RJ-45 backshells and UTP structured cabling.
init system and getty
The UNIX tradition is that the init system starts a process named getty ("get tty") which waits upon a connection to /dev/ttyS0, or whichever device is connected to the terminal. There is one getty listening per idle terminal. The getty prints /etc/issue, asks the user's name, and then calls login to ask for the user's password. If the password is good then login starts the sh ("shell") user interface. When the user exits sh then the call on /dev/ttyS0 is cleared down (by dropping DTR for a few seconds) and a new getty is started to wait for the next connection. Alternatively, if the shell is running and the serial terminal disappears (DCD drops) then the device sends a SIGHUP ("hang up") to the process which has the device open -- that is, sh -- to end its session.
Configure the init system to start a getty to listen for a incoming call. The procedure is is well described in a systemd blog. Copy the prototypical service file to the machine-specific directory:
$ sudo cp /usr/lib/systemd/system/serial-getty\@.service /etc/systemd/system/serial-getty\@ttyS0.service
Then edit /etc/systemd/system/serial-getty@ttyS0.s
ExecStart=-/sbin/agetty --8bits --flow-control -L=never ttyS0 9600
Then add the file into the systemd boot procedure:
$ sudo ln -s /etc/systemd/system/serial-getty\@ttyS0.service /etc/systemd/system/getty.target.wants/ $ sudo systemctl daemon-reload $ sudo systemctl start serial-getty\@ttyS0.service
Check your work:
$ systemctl status -l serial-getty@ttyS0.service serial-getty@ttyS0.service - Serial Getty on ttyS0 Loaded: loaded (/etc/systemd/system/serial-getty@ttyS0.service; enabled) Active: active (running) since Fri 2014-06-20 22:41:09 CST; 52min ago Docs: man:agetty(8) man:systemd-getty-generator(8) http://0pointer.de/blog/projects/serial-console.html Main PID: 896 (agetty) CGroup: /system.slice/system-serial\x2dgetty.slice/serial-getty@ttyS0.service └─896 /sbin/agetty --8bits --flow-control -L=never ttyS0 9600 Jun 20 22:41:09 host.example.edu.au systemd: Started Serial Getty on ttyS0
Note that neither agetty nor systemd place a lock file into /var/lock/lockdev/, which is a little disappointing.