CPSC 220, Fall 2012
Lab 10: Handling I/O Traps in Larc

For the final lab of the semester, you will again be programming in Larc assembly language. You will be writing a "trap handler." The code that you write will implement the system traps for printing and reading strings. For full credit, you will also implement printing of integers, and for extra credit, you can implement reading of integers.

For background, you should read Chapter 7 from the Larc manual, which was handed out in class. Some other information that you need can be found in the first section of this web page.

To begin the lab, you should copy the folder /classes/cs220/lab10-files into your account. It contains the file os.s, where you will do all your work for the lab, and two folders of assembly language programs that you can use for testing your work.

This is a two-week lab. Your work is due on Friday, December 7, the last day of classes. It should be copied into your homework folder, as usual.

This is an individual lab; you should not work with a partner.

Background

Compiling and running your code: The trap handler will run in kernel mode and will make essential use of the two registers that are reserved for the kernel and of the sysretn command that can only be used in kernel mode. This will generate a lot of warnings when you assemble the program. To eliminate all the warning messages, you can add the command option "-nowarn" to the asm command. That is, you should compile os.s with the command

asm -nowarn os.s

The trap handler that you write is not a complete program. It has to be loaded into Larc along with a user program, and you have to tell Larc to use your trap handler rather than the built-in simulated traps. To do this, run the user program using the option "-t os.s" with the sim command. For example, to run the program tests/hello-world.out, use the command:

sim -t os.out tests/hello-world.out

Polling the I/O devices: Larc does not use interrupts for I/O handling, so you will have to poll the keyboard and monitor devices. Consider the monitor as an example. The monitor device displays characters to the user. To print a string, you have to send one character at a time to the monitor device. And before you can send a character, you have to wait until the monitor is ready. The monitor uses two control registers for memory mapped I/O, the Monitor control Register, MCR, at address 0xFC02, and the Monitor Data Register, MDR, at address 0xFC03. When the monitor device is ready to accept a character, it sets bit number 15 of the MCR to 1. Before writing a character, you must loop until this becomes true. Since having a 1 in bit 15 is the same as having a negative number, you can simply loop until MCR is negative. Once that happens, you can write a single character to the MDR. For example, if the character is in register $s0, you could output the character to the monitor as follows:

         li $t0 0xFC02    # address of MCR
waitmcr: lw $t1 0($t0)    # get the MCR value...
         bgez $t1 waitmcr # ... and repeat as long as MCR > 0
         li $t0 0xFC03    # address of MDR
         sw $s0 0($t0)    # write the character from $s0 to the monitor

Remember that you have to do the wait loop for every individual character that you write! Reading from the keyboard is similar, except that you are reading a character instead of writing one, and you use the keyboard control registers, KCR at 0xFC00 and KDR at 0xFC01.

System Traps Halt and Print-String

To start work on the lab, open the file os.s for editing. This file already contains code that checks the type of trap that is being handled, does system initialization, and saves register values in the case where the type of trap is a syscall. Your main job is to implement the syscall traps. You will add code for that at the end of the existing code.

The first thing that you have to do is check register $1, which contains the trap number, to see which trap to handle. You should branch to a different label for each possible trap number. As a first step, you should get traps number 0 and 1 working.

If the value in register $1 is 0, you should halt the system. You can do that by storing a 1 in the PCR, at memory location 0xFFFF.

If the value in register $1 is 1, you should print a string. Remember that the address of the string is stored in register $2, and the maximum length of the string is in register $3. But remember! The address is given as a "user mode" value, but your code executes in kernel mode. This means that you have to add the mem_base value to the number in register $2 to get the actual starting location of the string. By the way, you are not required to preserve the values of $1, $2, and $3 while implementing this trap.

When printing the string, remember that you have to output characters until either the maximum string length is reached or you get to a zero that marks the end of the string.

When the string has been output, you need to restore saved register values and execute a sysretn instruction to return control to the user program.

You will want to write out a subroutine to print out a string, without the start-of-string adjustment or the restore-registers-and-return parts of the syscall implementation. You can use the subroutine to implement the print-string syscall, and you will also be able to use it in the next section and for printing a number. You will not need to use the stack for this subroutine, as long as you are careful about which registers you use. Remember that register values from the user program have already been saved.

Once you have this much of the trap handler written, you can assemble it and try it on the test programs tests/hello-world.out, tests/jump-test.out, and tests/print-program.out. (See the background section earlier on the web page for instructions on running a test program.)

Error Handling

There are two cases where the trap handler should print an error message and halt. One type of error is a bad trap number in a syscall. This type of error occurs for trap numbers outside the range 0 to 4; it should also occur for any valid trap number that you have not implemented. For this type of error, you should print the string at label badtrapmsg, which is already included in the .data section of os.s. The second type of error occurs when the type of trap is neither system initialization nor a syscall. This error is detected on line 25 of os.s, but no response is yet implemented. You should respond to this error by printing the string at label errormsg.

You should be able to print the error messages using a subroutine that you wrote for the previous section of the lab. After printing the error message, you should halt the machine by storing a 1 in the PCR.

The very short test programs tests/error-syscall.out tests/error-zerodiv.out can be use to test the response to the two types of error condition.

System Trap Read-String

Next, you should implement system trap number 3, for reading a string. For this trap, register $2 holds the address in memory where the string is to be stored, and register $3 holds the maximum length of the string. As for printing a string, remember that the memory location has to be adjusted to account for the switch from user mode to kernel mode!

For this trap, you need to use the keyboard device. You should read characters from the keyboard until you read a line-feed (character number 10). If the number of characters exceeds the maximum length of the input string, you should not store the extra characters. However, you should still read them! Don't forget to put a zero at the end of the stored string in memory. Also, you should never store the end-of-line character in memory; it is not considered to be part of the input string.

You can test your work with the sample program tests/repeat.out.

System Trap Print-Int

What you have done so far is worth up to 90% of the credit for the lab. For the final 10%, you should implement system trap number 2, for printing a number. Working with numbers is somewhat more difficult than working with strings, because of the special cases and the math involved in converting the number to an equivalent character representation.

If the number is zero, you should just output a 0. If the number is negative, you should output a "-", negate the number, and output the negated value. To print a positive number, you have to deal with the fact that the algorithm that produces the digits of the number produces them in reverse order. You can do that by storing the digits in memory as you generate them, and then printing the digits as a string. (Hint: Use the label endofnum to mark the end of the string of digits, and store the digits backward in memory from that point.) Also note that the digits are generated as numbers, but you need to have their ASCII codes in the string. To convert a numeric digit into its ASCII code, add 48 to the numeric value. Here is some pseudo-code for generating the digits of a positive number N:

while (N != 0) {
    digit = N % 10  // implement in assembly with the rem instruction
    store (digit + 48) in the next memory location
    N = N / 10
}

Note that for printing the string, once you have generated it, you can use the subroutine that you wrote in the first section of the lab.

You can test your work with the sample programs tests/powers-of-two.out and tests/print-ints.out.

Extra Credit: System Trap Read-Int

Finally, for extra credit, you can implement system trap number 4, for reading an integer from the keyboard. You should always read all input characters up to and including the next end-of-line, character number 10. If the first character in the input is a "-", you have to treat that as a special case. Other than that and the final end-of-line, the only characters in the input should be the digits 0 through 9. If any other character occurs, the input number should be zero. (The read-int system call never produces an error; it just returns 0 if the input is illegal.) The algorithm for reading an integer, assuming that all the input values are integers, is:

N = 0
while (more input) {
   digit = (next input char) - 48
   N = 10*N + digit
}

You need to add some error checking to this, to check for illegal characters. Note that the number that the user types can be out of the legal range for 16-bit values. You should just follow the algorithm and use the number that you get. For example, if the user inputs 1234567890, the number returned will be 722.

The return value must be put into register $1 before returning!

You can test your work with any of the programs in the folder xtra-tests