CS644 Tues, May 4
We are looking at the output side of the Xinu tty driver. Summary of last time’s analysis:
<ttyputc/ttywrite on top (producer), waits on iptr->osem when buffer full>
obuff in middle (shrared buffer),
<interrupt handler at bottom (consumer), signals on iptr->osem>
[interrupts are shut down to handle case of buffer empty]
The output interrupt handler is ttyoin, on pg. 174. The first two if’s are about echoes and flow control, so we’ll skip over them. The third if is of interest. It’s testing whether or not obuff is empty, and if not, dequeues a char from it and outputs it to the device, and does a signal on osem. Actually it does a more complicated thing than a simple signal per char, but that would work too. If obuff is empty, the code shuts down the output interrupts of the device.
The output to the device is done by cptr->ctbuf = char. This is memory-mapped i/o. We would use outb to the right i/o port.
We see signal(iptr->osem), but under some condition, signaln(iptr->osem, OBMINSP), what’s this all about?
That’s an optimization called watermarking, explained on pg. 175. Instead of a signal on each char, signals are saved up and signaled in a bigger batch, to avoid all the process switches of signal/char.
Let’s simplify the code by not using watermarking, echoes, or flow control, and also do x86 i/o.
void ttyoin(struct tty * iptr)
{
int port = iptr->hwdev->dvcsr; /* 0x2f8 for COM2 */
int ct;
if ((ct = scount(iptr->osem)) < OBUFLEN) { /* if something in obuff */
int ch = iptr->obuff[iptr->otail++]; /* dequeue ch from obuff */
if (iptr->otail >= OBUFLEN)
iptr->otail = 0;
outb(port + UART_TX, ch); /* output ch to hardware */
signal(iptr->osem); /* signal producer */
} else {
outb(port + UART_IER, UART_IER_RDI); /* turn off output ints,*/
/* leaving input ints on */
inb(port + UART_IIR); /* ack the transmitter, secondary method */
}
}
Here we are using inb and outb, as done in the Xinu sources. If you prefer (for the LP driver), you can use inpt and outpt as we did in the hardware programming: just be sure to include the SAPC library in the load to get access to these.
The above is of course a model for the lp driver int handler.
However, the lp driver is not identical, because the hardware protocol is different:
Serial line transmitter: interrupts when interrupts enabled or when transmitter goes ready. Can have transmitter interrupts on or off in the device.
Parallel port: Device has interrupts enabled all the time. Interrupt arrives when printer has finished accepting a new character.
Parallel Printer Handshaking Protocol:
--program/OS puts data in data port (port 0x378 for LPT1), thus putting signals on the 8 data lines of the cable.
--program/OS sends a “strobe” signal on strobe line, controlled by the printer control register, port 0x37a.
Strobe signal over time: ---------|______|------------- (on the STROBE wire in the cable)
where the dip is quite short, say .1 ms.
--printer (printer emulator) sees the strobe signal, takes in data from data lines, sends back an ACK signal on the ACK line:
ACK signal over time: ---------|______|------------- (on a ACK wire in the cable)
--The ACK signal causes an interrupt in the program/OS side. In the interrupt handler, the program/OS can put another char in the data port, etc.
Details on the STROBE: The strobe line is controlled by the LP_PSTROBE bit (bit 0) in the printer control register (LPT1_BASE + LP_CNTL, port 0x3fa for LPT1). However, it inverts the signal on the way out, so the bit value needs to be the opposite:
strobe bit value over time: _____|---------|_________
See testlp.c for programming details.
Note that, unlike the serial transmitter case, there is no special problem at the end of output. However, there is a little problem at the start. If the printer is idle, the first char needs to be output from the system call code. But if the printer is already printing, the char needs to be queued for later. So you need to track the printer’s idle/active state.
TTY Driver: Input Side
getc ----devtab---> ttygetc
read-----devtab---->ttyread
<ttygetc: system call code is the consumer: can wait on isem if ibuff empty>
ibuff: shared buffer
<ttyiin: input interrupt handler: the producer: can signal isem to report a new char>
A proper producer waits on a sem when the buffer is full, but the interrupt handler is not allowed to block, so we need another strategy.
No really nice solution: end up dropping input. Better than using up valuable kernel memory for input not being picked up by a process.
See ttygetc, pg. 166.
ttyiin: pp 176-180, very long function: what’s going on here?
Cooked vs. Raw Mode Terminal Input in Xinu, UNIX/Linux
Terminals can be in “cooked” or “raw” mode in Xinu or UNIX* (UNIX* also has an intermediate “cbreak” mode, see Wikipedia Cbreak_mode). Cooked is the usual state, with line editing (see Wikipedia Cooked_mode):
Cooked Mode Example: After prompt for name, I type:
Bettt<BS>y<CR>
Program gets “Betty\n”. That’s after “line editing” of “cooked mode input”, done in the OS. The program has no way to find out that I typed the <BS> to edit out the extra t.
Raw mode is sometimes called “hot keys”. No editing, program gets chars immediately. Emacs works with raw mode input.
Clearly raw mode is simpler—no editing in OS, just supply the most recently typed char not already delivered. <CR> and <BS> are not special characters.
Pg. 177: look at IMRAW code: just enqueue new char, signal it to syscall code.
rest of ttyiin code: the more complex cooked case, delays signals until <CR> seen, so that editing can be done.
BTW: how do you control whether the tty is in raw or cooked mode?
Answer: in Xinu, control(CONSOLE, TCMODER, 0); to set raw mode (defaults to cooked)
control ----devtab----> ttycontrol, pg. 184.
in UNIX*, use the ioctl syscall.
Next time: UNIX Device Drivers
Not much in Love about this, unfortunately. A little on pg. 235 about character devices vs. block devices. Let’s concentrate on char devices.