The UART alternative Substituting input from our PC’s serial-port for local keystrokes

advertisement
The UART alternative
Substituting input from our PC’s
serial-port for local keystrokes
when we do ‘single-stepping’
Problem background
• Last time we saw how the x86’s trap-flag
and debug-breakpoint registers could be
used to support ‘single-stepping’ through
program-code, to help us diagnose ‘bugs’
• But a conflict arises when we attempt to
debug code that handles keyboard-input
(as in the ‘isrKBD’ routine for Project 2)
• Our debugger also uses keyboard input!
Use another control device?
• To circumvent the contention for keyboard
control, we ask: can some other peripheral
device substitute for our PC’s keyboard as
a convenient ‘debugger-input’ source?
• Our classroom and CS Lab machines offer
us a way to utilize their serial ports as an
alternative device-interface for doing this
type of debugger ‘single-stepping’ task
Kudlick Classroom
08
09
10
15
04
05
06
07
01
02
03
16
17
18
19
11
12
13
14
20
28
29
30
24
25
26
27
21
22
23
lectern
Indicates a “null-modem” PC-to-PC serial cable connection
PC-to-PC communications
student
workstation
KVM cable
rackmount
PC system
student
workstation
KVM cable
‘null-modem’ serial cable
rackmount
PC system
ethernet cables
Using ‘echo’ and ‘cat’
• Our device-driver module (named ‘uart.c’)
is intended to allow unprivileged programs
that are running on a pair of adjacent PCs
to communicate via a “null-modem” cable
Transmitting…
$ echo Hello > /dev/uart
$_
Receiving…
$ cat /dev/uart
Hello _
Instructions in ‘isrDBG’
• Our ‘usedebug.s’ used these instructions
to support user-control of ‘single-stepping’
isrDBG:
.code32 # Our trap-handler for Debug Exceptions (interrupt-0x01)
…
# now await the ‘release’ of a user’s keypress
kbwait:
in
test
jz
$0x64, %al
$0x01, %al
kbwait
# poll keyboard-controller status
# a new scancode has arrived?
# no, continue polling controller
in
test
jz
$0x60, %al
$0x80, %al
kbwait
# else input the new scancode
# was it a key being released?
# no, wait for a keypress ‘break’
…
UART’s line-status
• The PC’s 16550 serial-UART interface has
a ‘status’ port and a ‘data’ port that behave
in a manner that’s similar to those ports in
the keyboard controller, so we can replace
instructions in our ‘isrDBG’ procedure that
accessed keyboard-controller ports with
instructions that access the UART’s ports
• This avoids ‘contention’ for the keyboard!
How to program the UART?
• Universal Asynchronous Receiver-Transmitter
See our CS630 course website at:
<http://cs.usfca.edu/~cruse/cs630f08>
for links to the UART manufacturer’s documentation
and to an in-depth online programming tutorial
• Software controls the UART’s operations
by accessing several registers, using the
x86 processor’s ‘in’ and ‘out’ instructions
The 16550 UART registers
Base+0
Divisor Latch Register
Base+0
Transmit Data Register
8-bits (Write-only)
Base+0
Received Data Register
8-bits (Read-only)
Base+1
Interrupt Enable Register
8-bits (Read/Write)
Base+2
Interrupt Identification Register
8-bits (Read-only)
Base+2
FIFO Control Register
8-bits (Write-only)
Base+3
Line Control Register
8-bits (Read/Write)
Base+4
Modem Control Register
8-bits (Read/Write)
Base+5
Line Status Register
8-bits (Read-only)
Base+6
Modem Status Register
8-bits (Read-only)
Base+7
Scratch Pad Register
8-bits (Read/Write)
16-bits (R/W)
UART’s I/O-port interface
The PC uses eight consecutive I/O-ports to access the UART’s registers
0x03F8
RxD/TxD
0x03F9
0x03FA
0x03FB
0x03FC
0x03FD
IER
IIR/FCR
LCR
MCR
LSR
interrupt
enable
register
receive buffer register and
transmitter holding register
(also Divisor Latch register)
line
status
register
line
control
register
modem
control
register
interrupt identification register
and FIFO control register
0x03FE
0x03FF
MSR
SCR
modem
status
register
scratchpad
register
Comparing ‘STATUS’ ports
Keyboard-controller’s status-register (i/o-port 0x64)
7
6
Parity
error
Timeout
error
5
Data is
from
Mouse
4
Keyboard
locked
3
Last byte
went to
0x64
2
System
initialized
1
0
Input
Buffer
Full
Output
Buffer
Full
Serial-UART’s line-status register (i/o-port 0x03FD)
7
6
Error in Transmitter
idle
Rx FIFO
5
THR
empty
4
3
Break
interrupt
Framing
error
2
Parity
error
1
0
Overrun
error
Received
Data
Ready
Changes to ‘isrDBG’
keyboard controls single-stepping
serial-UART controls single-stepping
isrDBG:
isrDBG:
…
kbwait: # poll for OUTB==1
in
$0x64, %al
test
$0x01, %al
jz
kbwait
# input new scancode
in
$0x60, %al
inwait:
…
# poll for RDR==1
mov
$0x03FD, %dx
in
%dx, %al
test
$0x01, %al
jz
inwait
# input new data-byte
mov
$0x03F8, %dx
in
%dx, %al
# ignore ‘make’ codes
test
$0x80, %al
jz
kbwait
# send back a reply
mov
$’#’, %al
out
%al, %dx
…
…
Using a Linux application
• To control our debugger from another PC,
we’ve written an application-program that
runs under Linux, and it uses our ‘uart.c’
device-driver to circumvent privilege-level
restrictions that Linux imposes on access
to i/o-ports by code which runs in ‘ring3’
• Our application is named ‘kb2cable.cpp’
• It also illustrates use of ‘i/o multiplexing’
Linux Kernel Modules
Linux allows us to write our own
installable kernel modules
and add them to a running system
Runs in ring3
Runs in ring0
application
device-driver
module
call
ret
ret
call
syscall
standard
“runtime”
libraries
Operating System
kernel
sysret
user space
kernel space
Linux char-driver components
Device-driver LKM layout
module’s ‘payload’
is a collection of
callback-functions
having prescribed
prototypes
function
function
function
...
the usual pair of
module-administration
functions
fops
AND
a ‘package’ of
function-pointers
init
registers the ‘fops’
exit
unregisters the ‘fops’
‘write()’ and ‘read()’
• Obviously your driver-module’s ‘payload’
will have to include ‘methods’ (functions)
which perform the ‘write()’ and ‘read()’
operations that applications will invoke
• You may decide your driver needs also to
implement certain additional ‘methods’
• For example, to support ‘i/o multiplexing’
our driver needed to implement ‘poll()’
UART initialization
• For two PC’s to communicate via the serial
null-modem cable, their UART’s must be
configured to use identical baudrates and
data-formats (i.e., 115200 bps, 8-N-1)
• Our ‘uart.c’ driver performs this essential
configuration in its ‘module_init()’ function
• Our ‘remotedb.s’ application does it in an
extra ‘real-mode’ subroutine we’ve added
The sequence of steps
(steps are described below in pseudo-code)
# initializing the UART communication parameters for 115200 bps, 8-N-1
outb
outb
outb
outw
outb
outb
0x00, UART_BASE+1
0xC7, UART_BASE+2
0x83, UART_BASE+3
0x0001, UART_BASE+0
0x03, UART_BASE+3
0x03, UART_BASE+4
# Interrupt Enable register
# FIFO Control register
# Line Control (DLAB=1)
# Divisor Latch register
# Line Control (DLAB=0)
# Modem Control
inb
inb
inb
inb
UART_BASE+6
UART_BASE+5
UART_BASE+0
UART_BASE+2
# Modem Status
# Line Status
# Received Data
# Interrupt Identification
l
The i/o-multiplexing problem
• Normally when an application ‘reads’ from
a device-file, that process will ‘sleep’ until
some data is available from that device
• So if data becomes available on another
device, it will not get processed because
the application is ‘blocked’ from being
given any CPU time by the OS scheduler
• This would spoil our ‘kb2cable’ application
‘read()’ causes ‘blocking’
read
Keyboard
write
‘kb2cable’
application
write
Serial UART
read
Whichever device this application attempts to read from, it
will get ‘blocked’ until that device has some data to deliver
Do multiprocessing?
• One idea for getting around this ‘blocking’
problem would be to just use the ‘fork()’
system-call to create separate processes
for reading from the different device-files
• Each process can sleep, and whichever
process receives any new data will be
awakened and scheduled for execution
• No changes needed to device-driver code
Different processes do ‘read()’
read
‘kb2cable’
parent- process
write
Keyboard
Serial UART
write
‘kb2cable’
child-process
read
Using multiple processes can overcome the ‘blocking-read’
problem, but complicates the code for program termination
Non-blocking ‘read’
• It is possible for the application to request
‘non-blocking’ read-operations – i.e., any
‘read()’ calls will immediately return with 0
as return-value in case no data is available
• The standard-input device-driver already
has support for this non-blocking option,
and it can be easily added to the ‘read()’
function in our serial UART’s device driver
Driver-code modification
ssize_t my_read( struct file *file, char *buf, size_t len, loff_t *pos )
{
static int
rxhead = 0;
// in case no new data has been received, then either
// return immediately if non-blocking mode is in effect
// or else sleep until some new data arrives (or until
// the user hits <CONTROL>-C to cancel execution)
if ( rxhead == ioread32( io + E1000_RDH ) {
if ( file->f_flags & O_NONBLOCK ) return 0;
if ( wait_event_interruptible( wq_recv,
inb( UART_LINE_STATUS ) & 0x01 )
return –EINTR;
}
…
Uses ‘busy-waiting’ loop
read
Keyboard
write
‘kb2cable’
application
write
read
Serial UART
Using the ‘nonblocking-read’ option overcomes the problem
of a sleeping task, but it wastefully consumes the CPU time
The ‘elegant’ solution
• The ‘select()’ system-call provides a very
general scheme for doing i/o-multiplexing
in a manner that avoids wasting CPU time
or making the program-code complicated
• But it does require adding an extra driver
‘method’ – the so-called ‘poll()’ function
The ‘select()’ arguments
• Using ‘select()’ requires an application to
setup an ‘fd_set’ object, which defines the
set of file-descriptors whose activity needs
to be monitored by the Linux kernel (in our
‘kb2cable’ application this would be just
the two device-files’ handles (the keyboard
and the serial UART)
• This ‘fd_set’ object becomes an argument
Using ‘select()’ in ‘kb2cable’
int
int
kbd = STDIN_FILENO;
uart = open( “/dev/uart”, O_RDWR );
fd_set
permset;
FD_ZERO( &permset );
FD_SET( kbd, &permset );
FD_SET( uart, &permset );
// keyboard ID
// device-file ID
// create an ‘fd_set’ object
// initialize it to ‘empty’
// add keyboard to set
// and add the nic to set
while (1) {
fd_set
readset = permset;
if ( select( 1+uart, &readset, NULL, NULL, NULL ) < 0 ) break;
if ( FD_ISSET( kbd, &readset ) ) { /* process keyboard input */ }
if ( FD_ISSET( uart, &readset ) ) { /* process network input */ }
}
How it works
• The ‘readset’ argument to the ‘select()’
system-call lets the kernel know which
device-drivers should have their ‘poll()’
method invoked
• Then each device-driver’s ‘poll()’ method
will perform a test to determine if any new
data is ready to be read from that device
• So the application calls ‘read()’ only when
a device is ready with data immediately!
In-class demo
• As a proof-of-concept demonstration, we
adding a “trivial” Interrupt Service Routine
for keyboard interrupts to our ‘remotedb.s’
program (we called it ‘addkbisr.s’)
• Then we used our ‘kb2cable’ application
running on an adjacent Linux machine to
do ‘single-stepping’ through ‘linuxapp.o’
-- and through the added ‘isrKBD’ handler
Download