CS444 Tues., Sept. 12

 

Intro to hw1—

Problem 0—warmup for 2.  Just duplicate a given remgdb session –due Friday, Sept.  15

Rest due following Friday, Sept. 22--

Problem 1—UNIX gdb for hello-world problem

Problem 2--finishing an i/o library for the SAPC.

 

Intro to hw1—

 

Problem 2--finishing an i/o library for the SAPC.

 

As a library, it implements certain useful functions for apps.  Its API is in io_public.h:

void ioinit(void);

int read(int dev, char *buf, int nchar);

int write(int dev, char *buf, int nchar);

also “control”

 

Read and write look very much like the same-named UNIX system calls, except for “dev” instead of “fd”.  The dev parameter is the device number, for the io library. Here dev can be TTY0 (value 0, standing for COM1) or TTY1 (value 1, standing for COM2).  See tty_public.h for the following:

 

#define TTY0 0

#define TTY1 1

 

In class, we looked briefly at the provided test program testio.c, to get the idea that it calls into the library using the API quoted above.

In "write(ldev, buf, nchar)", seen in testio.c, ldev is TTY0 or TTY1.  These numbers are defined in the header file tty_public.h provided with the hw1 project files.  These device numbers are special to hw1.

Recall from CS341 that the SAPC device numbers COM1 (value 1) and COM2 (value 2) are in a system of numbers set up in $pcinc/sysapi.h, which is included from $pcinc/stdio.h, and thus is in effect in any program with “#include <stdio.h>” or equivalent.

Note: you should have the environment pcinc defined for you by the ulab module, so if you can run mtip, you can also use $pcinc, for example by doing "cd $pcinc"  or "ls $pcinc" or "more $pcinc/stdio.h".  To see what it stands for, do "echo $pcinc".  It's just a directory in the UNIX filesystem.  Similarly $pcex stands for the file path to the SAPC examples directory.

The "system defined" (via stdio.h for SAPC) devices:

 

#define CONSOLE 100

#define KBMON 0   <---keyboard + monitor (we can’t use this via mtip)

#define COM1 1

#define COM2 2

 

CONSOLE is the current console, where the Tutor prompt shows up, and where the user is typing.  It is possible to run Tutor with console KBMON, COM1, or COM2, depending on what device the user types the first carriage return on at bootup time.  So by using CONSOLE, for example by fprintf(CONSOLE, “hi”); or equivalently printf(“hi”), we can talk to our user regardless of which way they are using our code. CONSOLE is the logical console, like stdout in UNIX.  All output via CONSOLE is sent to remote gdb as well as to the Tutor console.

 

When we use mtip, the Tutor console line is COM2.  So fprintf(CONSOLE, “hi”) and fprintf(COM2, “hi”) both output directly to the user.  You can put fprintf(COM1, “hi”) in your program, and it will work, but you won’t see the resulting output on COM1 (unless you remember a trick we used in CS241 mp5.)  (If you’re using remote gdb, you’ll be messing around with its protocol connection, to its great detriment.)

Even simpler, we can just put "printf("hi")" to write to the console.

 

SAPC Support Library

Uses device numbers: COM1, COM2, CONSOLE, etc., #define’d via stdio.h for SAPC

#include <stdio.h>    includes special stdio.h for SAPC, in $pcinc.  How does this work?  via compiler flag “–I <include-path>” in makefile

 

In the SAPC library, also arranged for access in the makefile:

  1. Standard C library calls, also available on UNIX, Windows, no modification:  printf, scanf, getchar, putchar, strcpy, strcmp, …
  2. C library calls, with device numbers instead of FILE *’s:  fprintf, fgets, …
  3. Special SAPC functions: sys_get_console_dev(), other sys_ functions, kprintf for debugging

 

When we use mtip, the Tutor console line is COM2.  So fprintf(CONSOLE, “hi”) and fprintf(COM2, “hi”) both output directly to the user.  You can put fprintf(COM1, “hi”) in your program, and it will work, but you won’t see the resulting output on COM1 (unless you remember a trick we used in CS241 mp5.)  (If you’re using remote gdb, you’ll be messing around with its protocol connection, to its great detriment.)

 

In some cases we want to make sure we’re talking to the user over a serial line.  We can ask Tutor what the current console device is by using the call “sys_get_console_dev()”.  You’ll see this called in echo.c and hw1’s testio.c.

 

hw1: In problem 2, you write code suitable for an i/o library layered on top of SAPC library.

 

As a library, it implements certain useful functions for apps.  Its API is in io_public.h:

void ioinit(void)

int read(int dev, char *buf, int nchar);

int write(int dev, char *buf, int nchar);

also “control”

 

Read and write look very much like the same-named UNIX system calls, except for “dev” instead of “fd”.  The dev parameter is the device number, for the io library. Here dev can be TTY0 (value 0, standing for COM1) or TTY1 (value 1, standing for COM2

 

Look at the API again.  This can be called a “device-independent” i/o library because the interface is generic, not specific to any particular device.  The actual device in use can be determined at runtime, for example, by asking the user for a device number.

 

testio.c is the test program calling through the API.  After making sure it’s talking to the user over a serial line, device ldev, it does a write(ldev, “hi!\n”, 4) as a first test.  As delivered, output works but without using interrupts.  Your job is to reimplement it with interrupts.

 

Then testio.c does a read(ldev, buf, 10) requesting 10 bytes from the user.  It is implemented with interrupts already, but you need to switch it from direct use of rbuf and tbuf to use of Queues from queue.c.  Also it doesn’t wait properly for all the chars requested.  Note the busy loop before the read—this wastes time so you have a chance to input a few characters before read executes.

 

The edits you need to make are all in tty.c, in ttyread and ttywrite and the interrupt handler.  A call to read ends up at ttyread and a call to write at ttywrite.  I’ll let you trace out how this happens.  Look at io.c and ioconf.c.  This shows how device independent i/o systems work—the device number specifies which device-specific function to actually execute.  Here the device-specific functions are ttyread and ttywrite, and the bundle of tty-functions in tty.c is called the tty driver, or serial port driver.

 

Dataflow to be handled by Queues

Input:  chars arrive in the interrupt handler, where they are each enqueued into the input queue.

ttyread dequeues each char, and puts it in the user buffer.

What’s the user buffer?  That’s described by the parameters of ttyread, or read itself:  “char *buf, int nchars”.  The app code (here testio.c) is requesting a bufferful of chars by calling read.  The code as provided already delivers some chars to the user buffer.

 

Output:  chars arrive from the user, in the user buffer, i.e. buf and nchars as arguments.

The code in ttywrite enqueues them in the output queue.

Eventually, your new interrupt code will dequeue them in the interrupt handler.  As provided, the code just outputs them from ttywrite.

 

C Objects, a quick intro/review.

 

Here the Queue ADT in C is provided in the queue subdirectory, along with a unit test testqueue.c.  queue.h defines the Queue API and the Queue type.

 

You know how to do objects in Java, what about in C?  Recall the Chip object in CS241.  Also the Cmd object for tutor.

 

Example: Rectangle objects

 

typedef struct rect {

     int x1, y1, x2, y2;

} Rectangle;

Rectangle *recp;

 

recp->x1 = 10;

 

Watch out!, there’s no (validly-allocated) memory pointed to by recp!  This is a garbage pointer, a pointer that points nowhere good.  Using it usually causes segmentation violations on UNIX and similar exceptions on Windows.  But on SAPC, all user memory is writable, so it may quietly work to damage some memory.

 

Normally under UNIX or Windows, we could malloc space for the Rectangle, but we have no malloc in the SAPC library.  Anyway, malloc is quite expensive in CPU, and we can easily avoid using it.  Just set up whole objects like this:

 

Rectangle rect;

 

Now we can put

 

rect.x1 = 10;

 

Similarly, we can set up larger objects that contain Rectangles:

 

typedef struct view {

     Rectangle first, last;

} View;

View v;

 

v.first.x1 = 10;  /* set coord of v’s first Rectangle */

v.first = rect;    /* set whole Rectangle by struct copy in C */

 

Setting up whole objects like this may seem strange after using Java a long time, but it’s good to really know C too!

There is more we should cover on this topic.  We have no encapsulation here, so calling this an “object” is hard to defend to object-oriented people.  It’s an object in a practical sense that it brings together related data, and has an API that describes what can be done with the data—see queue.h.

 

 

 Programming the Hardware:  Using I/O Ports

 

Ref: Notes on using C to access hardware registers, Tan, 5.1 to pg. 274.

 

The PC uses I/O ports for most I/O devices, not memory-mapped I/O.  It is capable of memory-mapped I/O, however.

 

First look at $pcex/echo.c.  You can build it the same basic way as test.c.  Just have $pcex/makefile as well as echo.c in a directory and “make C=echo”.  You’ll get echo.lnx to download.