Computer Labs 2013/2014 - 1st Semester
Lab 4: The PC's Keyboard

1. Objectives

The objectives of this lab are twofold. First that you learn the operation of the PC's keyboard and how to use its low level interface. Second, that you get a better understanding of the interrupt mechanism and that you learn how to use it in the context of the PC's keyboard.

2. What to Do

Write in C language several functions that use the PC's keyboard low level interface. The key functionality to implement is:

  1. Read and display the scancodes, both the make codes and the break codes, generated by the PC's keyboard;
  2. To change the state of the indicator LEDs of the PCs keyboard

Unlike in previous labs you are not given the prototypes of the functions to implement: the specification of these functions is part of your job. However, to make the task of grading your assignment feasible you are required to implement the following test functions:

  1. int kbd_test_scan()
  2. int kbd_test_leds(unsigned int n, unsigned short *toggle)

These functions are declared in header file test4.h, and file test4.c contains their implementation stubs. You may find it convenient to add your test code to that file; this way you will avoid mistakes in their definition. Section 5 describes what these functions should do.

2.1 Class Preparation

So that you can accomplish this lab's objectives, you should do some homework. In addition to read, and understand, this script and the class notes, you should:

  1. Create folder lab4 at the top level of your SVN repository;
  2. Write the code that reads and displays the scancode from the KBC, as well as its testing code.
  3. Add the files you created in point 2 to the folder created in point 1

3. The PC Keyboard and its Controller

Whenever a user presses or releases one of its keys, the keyboard sends a scancode to the KBC. The KBC puts that code in a register, and generates an interrupt, if configured to do so. It is then up to the interrupt handler to read the scancode from the KBC. Although the KBC can be used in polled mode, this is seldom the case.

3.1 Scancodes

A key's scancode depends only on the position of that key in the keyboard. It is different from the ASCII code (or other common encoding) with which it is labeled. The conversion between the key's scancodes and the code of the character on that key is usually done by the operating system using a keymap. For example, by default Minix 3 uses the US keyboard keymap. The Minix 3 images you are using in LCOM use a "handcrafted" Portuguese keymap, because Minix 3 does not include any in its distribution. The advantage of this approach is clear for keyboard manufacturers: a key in a given position, always generates the same scandcode, independently of whether the keyboard uses the US layout or another layout.

Furthermore, the scancode generated when a key is pressed is different from the scancode generated when that key is released. To distinguish between them, we call the former make code and the latter break code, a commonly used terminology. Usually, the break code of a key differs from the make code of that key in that the MSB of a break code is set whereas that of the make code is not. For example, the make code of the ESC key is 0x01 whereas its break code is 0x81.

Using different make and break codes provides a lot of flexibility. For example, a keyboard needs not generate a different scancode for a key when the Shift key is pressed. Rather, it generates a make code for Shift and it is then up to the keyboard driver to do the necessary mapping between the reception of the Shift make code and the reception of its break code.

Most PC's scancodes are one byte long, although some special keys have longer scan codes. Two-byte long scancodes usually use 0xE0 as their first byte. This prefix is used in both the make and the break codes. Again, the difference between the make and the break codes of a key is in the MSB, now of the second byte of the scancode. Some keyboards have keys that generate even longer scancodes, but you need not worry about it in LCOM.

The scancodes that we have described are known as Set 1, that is are the set of scancodes of the PC-XT. Another set of scancodes, known as Set 2 appeared with the PC-AT and is widely used. However, the KBC can be programmed to translate Set 2 scancodes, received from the keyboard, to Set 1 scancodes, so that the keyboard device drivers need only know about Set 1 scancodes. Minix 3 keyboard driver configures the KBC to translate Set 2 scancodes, thus, as long as you do not change the KBC configuration in that respect, your code needs to handle only Set 1 scancodes.

3.2 The i8042: The keyboard controller (KBC)

In modern PCs the communication between the keyboard and the processor is mediated by an electronic component that provides the functionality of the i8042, the keyboard controller (KBC). The communication between the KBC and the keyboard (actually, a microprocessor embedded in the keyboard) is by means of a serial communication protocol that is not the object of this lab.

In this lab, you need only to interface with the "i8042", which was thoroughly described in class. In addition to read the class notes, you may find it useful to read the 8042 functional description from the IBM Technical Reference Manual and the data sheet of an 8042-compatible IC. There are several other resources on the Web, including those mentioned in the class notes. You may wish to take a look at these, if you find the material I wrote insufficient.

4. Minix 3 Notes

4.1 Disabling the Default Interrupt Handler

When Minix 3 boots, its terminal driver configures the KBC and installs its own keyboard interrupt handler. This way, users are able to login to Minix and use the different virtual terminals. On one hand this is great: you do not need to configure the KBC. On the other hand this raises an issue: how can your code read the scancodes? Indeed, when the user presses/releases a key, the KBC will generate an interrupt, and the KBC interrupt handler of the terminal driver will execute and read the scancode, and possibly echo a character in the terminal.

A solution to this issue, is for your driver to subscribe the KBC interrupts, as described in Section 5.2 of Lab 3. However, now it should specify not only the IRQ_REENABLE policy but also the IRQ_EXCLUSIVE policy. As a result, the standard Minix 3 KBC interrupt handler will not be notified of the occurrence of KBC interrupts, thus preventing it from interfering with your code.

To ensure that you can use Minix 3 virtual terminals once your program is done, your code must cancel its subscription of the KBC interrupt before exiting, by calling the sys_irqrmpolicy() kernel call.

4.2 Measuring Time

Both the KBC and the keyboard may take some time to respond to a command. For example, IBM's specification of the i8042 requires the keyboard to respond to a command in 20 ms. Thus your code should not expect to get a response immediately after issuing a command. A simple approach is for your code to wait indefinitely, or until the KBC reports a time-out. A more fault-tolerant approach is for your code to give enough-time for the KBC or the keyboard to respond, retry a few times on time-out, and finally give up. Given that the time intervals to consider are in the order of tens of ms, it is not appropriate to use sleep(), which measures time intervals whose duration is a multiple of a second. Instead, you can use the function tickdelay() of Minix 3's libsys as follows:

#include <minix/sysutil.h> #define DELAY_US 20000 tickdelay(micros_to_ticks(DELAY_US);

This function is similar to sleep() in that it blocks the process that executes it for the time interval specified in its argument. After that time interval, the process resumes execution by executing the instruction that follows tickdelay().

5. Testing Code

So that we can grade your work, you are required to implement the following testing functions. We will develop the code that will call them, so make sure that your implementation matches their prototypes.

5.1 test_scan()

The purpose of this function is to test that your code is able to read the scancodes from the KBC using an interrupt handler.

Thus, it should first subscribe the KBC interrupts, as described in the previous section.

Then it should print the scancodes on the console, indicating whether or not the scancode is a make or a break code. The output should look like the following:

Makecode: 0x01 Breakcode: 0x81

The test_scan() function should exit when the user releases the Esc key, whose break code is 0x81. For the reasons described in the previous section, it should cancel the subscription of the KBC interrupt before terminating.

IMP. If you do not cancel the subscription of the KBC interrupt, Minix's keyboard interrupt handler will not be notified of interrupts on the keyboard and therefore you will not be able to use any of the virtual terminals on the virtual machine. However, you will be able to use the command line interface (CLI) via a remote shell, for example on a Linux terminal, or in Eclipse. The remote shell uses the keyboard on a system different from Minix.

5.2 test_leds(unsigned int n, unsigned short *toggle)

The purpose of this function is to test your code that changes the state of the keyboard indicator LEDs. Its arguments are an array toggle and n, the number of elements of toggle. Each element of toggle specifies which indicator LED should have its state toggled.

The function test_leds() should process each element of the toggle array once per second. For example if toggle's elements are 0,1,1,1,1,0, your program should turn on the LED corresponding to bit 0, then 1 second later turn on the LED corresponding to bit 1, then 1 second later turn off the same LED, then 1 second later turn on the same LED, then 1 second later, turn it off, and 1 second later turn off the LED corresponding to bit 0.

After processing all elements of the toggle array, your function should exit.

To measure the time you can use Timer 0 interrupts, or the sleep() function, but in the latter case the maximum possible score will be 90%, rather than 100%.

6. Compiling your Program

Follow the procedure described for the last lab (of course now you should use directory lab4 instead of directory lab3), but do not forget to modify the Makefile.

7. Configuring your Program

Your program invokes functions that are privileged. Thus, before you can run it, you need to add a file named lab4 to the /etc/system.conf.d/ directory to grant your program the necessary permissions.

Unless you use privileged functions that are not really necessary, the following entry is enough:

service lab4 { system DEVIO IRQCTL ; ipc SYSTEM rs vm pm vfs ; io 40:4 60 64 ; irq 0 # TIMER 0 IRQ 1 # KBD IRQ ; uid 0 ; };

8. Submission

As you complete the different milestones of this lab, you should commit your work to the SVN repository in Redmine.

Note that SVN is able to keep the different versions of your code. Remember that you have a tolerance of only 5 minutes at the end of your class.


This lab is based on a lab by João Cardoso and Miguel P. Monteiro for DJGPP running on Windows98.