Assignment 2 (Screen and keyboard drivers)
due 16 February at noon
To retrieve the files for assignment 2, open up the terminal application and type the following commands:
cd ~/cs643 svn update
Subversion should download a new folder called a2
.
In this assignment, you will develop some of the screen and keyboard drivers for our GeekOS kernel. I highly recommend doing the tasks in the order listed below, and test your code carefully after each one.
ABOUT SCORING THIS ASSIGNMENT: I'm happy to award partial credit, so even if you can do only half the tasks below, be sure to commit them ON TIME. However, I severely punish code that does not compile. If the latest code you have committed before the deadline does not even compile, then the best score you can hope to get is 25 out of 100. For example, if you accomplish tasks 1-6, but haven't quite figured out 7-8, make sure that any non-compiling code is commented out, so it doesn't prevent me from testing your solutions for 1-6.
Table of Contents
1 Set up
First, build the kernel as is:
cd ~/cs643/a2/build make
This will copy the fd.img
to your cs643host
folder, where you can
run it in VirtualBox as before. After the BIOS boot screen, you will
see an LIU Computer Science
banner in the center, and an exclamation
mark in the upper left corner of the screen.
The files we'll work on mostly are screen.c
and keyboard.c
, both
in the
cs643/a2/src/geekos
folder. Open them in your editor. In main.c
, you will notice that
the kernel uses Banner()
to write the banner using the VIDMEM
technique as before. Then, after initializing some things, it tries to
print out Welcome to GeekOS!
but since the Print()
function does
not work correctly yet, all that remains is the final exclamation
mark! Essentially, the Print()
function is writing each character
to the same location on the screen: the upper left. We'll fix that in
a bit.
But now, if you type some keys, you will see that exclamation mark
change to the key you typed. Now, the main function is in a loop
waiting for a key press to be reported from the keyboard driver, and
then calling Put_Char()
to place that key on the screen. Again, for
now it always puts it in the same position.
You may notice that if you try to hit the shift key, or return key, or
tab, or any other special (not alpha-numeric) keys, then funny
characters show up in that position. Even more annoying, shift and
caps lock will not work at this stage. If you hit Shift-k, you may
get a lowercase k
and a funny character for shift, but no upper-
case k
. You don't get anything for free in an OS kernel, because
there's nothing running underneath you! So we'll fix up some of these
things in this assignment. You can quit VirtualBox now and get to
hacking!
2 Clear the screen
Now open up screen.c
. Near the top, you will see a structure called
Console_State
. It is the type of a global variable s_cons
that
keeps track of the current row, column and attributes (colors). So,
we use expressions like s_cons.col
, s_cons.row
, and
s_cons.currentAttr
.
Further down in the file, the function Init_Screen()
[written for
you] initializes this global structure, so that the initial row and
column are set to 0, and the current attribute is set to
DEFAULT_ATTRIBUTE
(gray on black).
Your task is to write the function Clear_Screen()
; the function
definition starts at about line 34. You should set every character in
video memory to the space character and every attribute to the
currentAttr
in s_cons
. In other words, it's a for
loop.
Once you have written Clear_Screen()
, build it and test it by
running the kernel again. This time, the LIU Computer Science
banner will disappear after boot, and you should see just the
exclamation point in the upper left. The keys and everything else
will behave as before, but at least the screen is empty now.
3 Write a character
Next step is to get the Print()
functions working, by having the
current position move on to the next column after each character is
printed. This is controlled by the Write_One_Char()
function
starting just below Clear_Screen()
.
You can see that it is currently hard-coded to put the character in
VIDMEM[0]
, which of course corresponds to the upper-left corner.
What it should do in reality is check the values of s_cons.row
and
s_cons.col
, and put the character c
in that position on the
screen.
You'll have to figure out what index to use with VIDMEM
in order to
place the character at the row and column designated in s_cons
.
Then, you'll have to update s_cons
, so that the next character to be
written appears in the next column. Once the column number reaches
the maximum (NUMCOLS
), you can reset it to zero and increment the
current row.
Build and test the kernel again. If you correctly move on to the next column after each character, you should see something like this across the first row:
Initializing IDT...XInitializing keyboard...XWelcome to GeekOS!
where the X
characters above are actually some funny-looking box
character. If you type more characters, they will continue across the
row and wrap to the next row. You may notice that each character you
type appears twice, and special characters still will not work; that's
fine for now.
4 New line
The funny-looking box character appears because your video driver
doesn't know how to interpret the newline character (\n
)! So,
inside Write_One_Char()
, if the parameter c
is the newline
character (check for \n
OR \r
), instead of trying to print that on
the screen, you should simply increment the current row number and set
the column back to zero. Build and test this. Now the output should
look like:
Initializing IDT... Initializing keyboard... Welcome to GeekOS!
This means that the newlines are working, at least from literal
strings in your kernel code. Now type some characters and press
enter. If you also handled the \r
as described above, then you
should see the cursor move to the next line upon hitting enter.
5 Key up vs. key down
Now open keyboard.c
. We're going to fix some of the problems with
the keyboard driver. The function Keyboard_Interrupt_Handler
that
starts at about line 166 is the interrupt handler that gets installed
for the keyboard interrupt. That means that, whenever a key is
pressed (or released), this piece of code is called.
Its task is to decode the key press into something more useful to the
rest of the kernel (and, eventually, other applications) and then put
the key press onto a queue where it can be retrieved by a waiting
application. The Main()
function in main.c
does just that: it
waits for a key press to show up in the queue, and then outputs it
onto the screen.
There are two types of codes we have to worry about: scan codes and key codes. The scan codes are lower level; they're the ones that the keyboard hardware communicates to us via the system bus. Basically, each physical key gets a number.
Within this keyboard driver, we need to map scan codes to key codes.
Why are they different? Well, sometimes the same physical key can be
thought of as two distinct keys, such as 4
and $
for example.
When you type $
, you hardly have to think about hitting the shift
key, and it's most convenient if the applications see it that way too.
The tables s_scanTableNoShift
and s_scanTableWithShift
(starting
around line 68) are for converting scan codes to key codes. The
conversion you use depends on whether a shift key is currently
pressed.
So, let's do this first: get rid of the doubled-up characters when you type. This happens because an interrupt is generated both when a key is pressed (or repeatedly when held down) and when it is released. For most keys, we don't really care when they're released; we output the character as soon as they are pressed. However, for keys like shift, control, alt (called modifier keys), we will need to note when they are released.
In the interrupt handler, you'll see a fragment that checks the scan
code and then sets release = true
. When this happens it means the
interrupt occurred because of a released key, not a pressed key. Now
look further down to where it says “Add the keycode to the queue.” We
do not want to do this if this was a released key, so wrap that
part (it's just that comment and one line of code under it) in an if
statement that first checks the release
flag. Keycodes should be
enqueued only if release
is false.
Now when you build and test, you should be able to type hello world
onto the screen once your kernel has booted, and the characters will
not be doubled up anymore.
6 Shift into gear
Now we will try to get the shift key working correctly, so you can
type Hello, @#$%
onto the screen.
There is a global variable s_shiftState
defined in keyboard.c
. We
can use this to keep track of which modifier keys (shift, control,
alt) are pressed down at any given time. The bits within that
variable are LEFT_SHIFT
, RIGHT_SHIFT
, etc. There is also a
definition of SHIFT_MASK
, which means LEFT_SHIFT
or RIGHT_SHIFT
,
because in most cases we don't care which one is pressed.
So here is step 1: in Init_Keyboard
, s_shiftState
is initialized
to zero. Initialize it instead to LEFT_SHIFT
. We're just doing
this temporarily, acting as if the shift key is always pressed.
Next, back in the interrupt handler, when you look up the keycode in
the conversion table, you're going to use one table if shift is
pressed, and the other if it is not. So put an if
statement, and if
s_shiftState & SHIFT_MASK
is true, then you'll look up the code in
s_scanTableWithShift
; otherwise use s_scanTableNoShift
.
Build and test. If you followed these instructions correctly, then
every key you type will be interpreted as if shift were held down.
That is, typing hello 12345
will come out as HELLO !@#$%
.
Pressing a shift key will still get you a funny character. That's the
next thing to fix.
7 Shift back again
Back in the interrupt handler, after looking up the keycode but BEFORE
adding it to the queue, we need to do some processing to figure out if
this is a shift key. Basically, if the keycode is KEY_LSHIFT
, you can
call
Set_Or_Clear_Shift_State( LEFT_SHIFT, release )
and that will modify the flag in the global s_shiftState
for you. Do
the same for KEY_RSHIFT
and RIGHT_SHIFT
and, if you like, the control
and alt keys. Note that KEY_LSHIFT
and LEFT_SHIFT
are not the same:
KEY_LSHIFT
is the keycode of the left shift key, and LEFT_SHIFT
is
just a flag that we set in the global s_shiftState
.
You should then enqueue the keycode only if it is not one of
these modifier keys (and not a key release, as we had before). So you
may want to use an if/else
chain or a switch
statement here.
Now you should be able to restore the s_shiftState
initialization in
Init_Keyboard
. Set it back to zero.
Build and test. Now, pressing shift no longer outputs a funny
character (if your if/else
or switch is correct) but it will work
the way it is intended. You should be able to type HeLlO WoRlD
and other such things.
8 Scroll away
There is just one more step, back in the video driver. Run the kernel you have so far, and type enter many times, until the cursor gets to the last row. Type something on this row, and press enter. You'll see the cursor disappear off the bottom edge of the screen! You can keep typing, but it doesn't show up anywhere.
At this point, the characters you type are being written to memory, but it is outside the usable portion of video memory that corresponds to the screen. If you continue typing, you may eventually overwrite something important in memory and cause the whole thing to crash! (But how would you know, since it doesn't do anything else useful at this point…)
Anyway, what we really want is for the screen to scroll when we hit enter on the last line. That is, every other row should move up by one (the top-most line disappears), and the bottom-most row is cleared out and the cursor placed at the beginning of that row.
So, this is your next task. In your Write_One_Char()
function in
screen.c
, you probably increment the current row number whenever the
user hits enter, or moves past the last column on the screen:
You'll have to add a check here, so that if we're already on the last
row, we should call the Scroll() function instead of
incrementing the row. I have not implemented Scroll() for you, but
just make it a void function, somewhere above Write_One_Char()
.
Then try writing a loop that will go through all the cells on the screen (except those in the first row), and copy each value up to the previous row. Then all that's left is to clear out the bottom row.
That's it, you're done. Be sure to commit early and often. If you can't finish everything before the deadline, just do what you can and commit it, but MAKE SURE IT COMPILES!
If you finish early, you may want to work on some additional things. For example, the arrow and backspace keys don't work. Caps lock doesn't do anything. Maybe you want to have the escape character clear the screen. Feel free to play!