Assignment 2

due at 23:59 on Wed 15 Feb

To retrieve the files for assignment 2, open up the terminal application and type the following commands (reminder: the dollar signs represent the prompt, don’t type them).

$ cd ~/cs643
$ git pull

Git 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.

1. Set up

First, build the kernel as is:

$ cd ~/cs643/a2/build
$ make

This will copy the fd.img to your host 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 (TODO:getting circle) 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!

Reminder: here’s how to commit:

liucs:~/cs643/a3/build$ git commit -am "my assignment 2"
liucs:~/cs643/a3/build$ git push
comments powered by Disqus

 

©2012 Christopher League · some rights reserved · CC by-sa