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

31
The UART alternative Substituting input from our PC’s serial-port for local keystrokes when we do ‘single-stepping’
  • date post

    20-Dec-2015
  • Category

    Documents

  • view

    224
  • download

    3

Transcript of The UART alternative Substituting input from our PC’s serial-port for local keystrokes when we do...

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 16 17 18 19 20 28 29 30

04 05 06 07 11 12 13 14 24 25 26 27

01 02 03 21 22 23

Indicates a “null-modem” PC-to-PC serial cable connection

lectern

PC-to-PC communications

rackmountPC system

studentworkstation

KVM cable

rackmountPC system

studentworkstation

KVM cable

‘null-modem’ serial cable

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

$ echo Hello > /dev/uart$ _

$ cat /dev/uartHello _

Receiving…Transmitting…

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 keypresskbwait:

in $0x64, %al # poll keyboard-controller statustest $0x01, %al # a new scancode has arrived?jz kbwait # no, continue polling controller

in $0x60, %al # else input the new scancodetest $0x80, %al # was it a key being released?jz kbwait # 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

• Software controls the UART’s operations by accessing several registers, using the x86 processor’s ‘in’ and ‘out’ instructions

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

The 16550 UART registers

Transmit Data Register

Received Data Register

Interrupt Enable Register

Interrupt Identification Register

FIFO Control Register

Line Control Register

Modem Control Register

Line Status Register

Modem Status Register

Scratch Pad Register

Divisor Latch Register 16-bits (R/W)

8-bits (Write-only)

8-bits (Read-only)

8-bits (Read/Write)

8-bits (Read-only)

8-bits (Write-only)

8-bits (Read/Write)

8-bits (Read/Write)

8-bits (Read-only)

8-bits (Read-only)

8-bits (Read/Write)

Base+0

Base+0

Base+1

Base+2

Base+2

Base+3

Base+4

Base+5

Base+6

Base+7

Base+0

UART’s I/O-port interface

RxD/TxD IER IIR/FCR LCR MCR LSR MSR SCR

The PC uses eight consecutive I/O-ports to access the UART’s registers

0x03F8 0x03F9 0x03FA 0x03FB 0x03FC 0x03FD 0x03FE 0x03FF

scratchpad register

modem statusregister

line statusregister

modem controlregister

line controlregister

interrupt enableregister

interrupt identification register and FIFO control register

receive buffer register and transmitter holding register(also Divisor Latch register)

Comparing ‘STATUS’ ports

Parityerror

Timeouterror

Data isfrom

Mouse

Keyboardlocked

Last bytewent to

0x64

Systeminitialized

InputBuffer

Full

Output Buffer

Full

7 6 5 4 3 2 1 0

Error inRx FIFO

Transmitteridle

THRempty

Breakinterrupt

Framingerror

Parityerror

Overrunerror

ReceivedData

Ready

7 6 5 4 3 2 1 0

Keyboard-controller’s status-register (i/o-port 0x64)

Serial-UART’s line-status register (i/o-port 0x03FD)

Changes to ‘isrDBG’

isrDBG:…

kbwait: # poll for OUTB==1in $0x64, %altest $0x01, %aljz kbwait

# input new scancodein $0x60, %al

# ignore ‘make’ codestest $0x80, %aljz kbwait

isrDBG:…

inwait: # poll for RDR==1mov $0x03FD, %dxin %dx, %altest $0x01, %aljz inwait

# input new data-bytemov $0x03F8, %dxin %dx, %al

# send back a replymov $’#’, %alout %al, %dx

keyboard controls single-stepping serial-UART controls single-stepping

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

application

standard“runtime”libraries

call

ret

user space kernel space

Operating Systemkernel

syscall

sysret

device-drivermodule

Linux allows us to write our own installable kernel modulesand add them to a running system

callret

Runs in ring3

Runs in ring0

Linux char-driver components

init

exit

fops

function

function

function

. . .

Device-driver LKM layout

registers the ‘fops’

unregisters the ‘fops’

module’s ‘payload’ is a collection of callback-functions having prescribed prototypes

AND

a ‘package’ of function-pointers

the usual pair of module-administration functions

‘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

# initializing the UART communication parameters for 115200 bps, 8-N-1

outb 0x00, UART_BASE+1 # Interrupt Enable registeroutb 0xC7, UART_BASE+2 # FIFO Control registeroutb 0x83, UART_BASE+3 # Line Control (DLAB=1)outw 0x0001, UART_BASE+0 # Divisor Latch registeroutb 0x03, UART_BASE+3 # Line Control (DLAB=0)outb 0x03, UART_BASE+4 # Modem Control

linb UART_BASE+6 # Modem Statusinb UART_BASE+5 # Line Statusinb UART_BASE+0 # Received Datainb UART_BASE+2 # Interrupt Identification

(steps are described below in pseudo-code)

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’

‘kb2cable’ application Keyboard Serial UART

Whichever device this application attempts to read from, it will get ‘blocked’ until that device has some data to deliver

read

readwrite

write

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()’

‘kb2cable’ parent- process

Keyboard Serial UART

‘kb2cable’ child-process

read

read

write

write

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

‘kb2cable’ application

Keyboard

Serial UART

read

read

write

Using the ‘nonblocking-read’ option overcomes the problem of a sleeping task, but it wastefully consumes the CPU time

write

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 kbd = STDIN_FILENO; // keyboard ID int uart = open( “/dev/uart”, O_RDWR ); // device-file ID

fd_set permset; // create an ‘fd_set’ object FD_ZERO( &permset ); // initialize it to ‘empty’

FD_SET( kbd, &permset ); // add keyboard to setFD_SET( uart, &permset ); // 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