Contracts: As was the case with Project 0, we have once again included contracts.h for those students who wish to use it. Note the contracts will be enforced only when DEBUG is #defined (which is not true by default; see config.mk for how to turn DEBUG on).
Asserts (pre-condition failure): To that end, we have extended assert.h with four macros that always evaluate the specified expressions, regardless of "debug settings", and tastefully invoke panic() as required.
intel-sys.pdf: some instruction must be executed in a more previleged level else fault occurs.
Segments: In this project, since no code will execute in user mode, we will use only the two PL 0 segments. To refer to these segments when configuring kernel data structures (such as IDT entries), use the SEGSEL_KERNEL_CS and SEGSEL_KERNEL_DS constants defined in 410kern/x86/seg.h. These numbers are segment selectors, which are simply indices into another processor data structure, the global descriptor table (GDT). The GDT is the place we have defined our segments. You do not have to reconfigure it or know its format, however. (https://www.cs.cmu.edu/~410/doc/segments/segments.html)
Port: Both the timer and the keyboard use I/O ports (b - byte, w - word/short, l - long/int, you probably only need outb() and inb().). (410kern/x86/asm.h), more information on I/O ports, consult chapter 10 of intel-arch.pdf
Hardware Interrupt: When a device wants to raise a hardware interrupt, it communicates this desire to one of two programmable interrupt controllers (PICs) by asserting some control signals on interrupt request (IRQ) lines. The PICs are responsible for serializing the interrupts (taking possibly simultaneous interrupts and ordering them), and then communicating the interrupts to the processor through special control lines. The PICs tell the processor that a hardware interrupt has occurred and which request line the interrupt occurred on so the processor knows how to handle the interrupt. How devices are assigned to particular interrupt request lines is extremely complicated, but there are some conventions which are usually followed, displayed below.
PIC 1 0 Timer 1 Keyboard 2 Second PIC 3 COM2 4 COM1 5 LPT2 6 Floppy Disk 7 LPT1
PIC 2 8 Real Time Clock 9 General I/O 10 General I/O 11 General I/O 12 General I/O 13 Coprocessor 14 IDE Bus 15 IDE Bus
To handle: read IDT (with privilege level) to get the handler (DPL, execute as quickly as possible so that the processor is free to accept future interrupts, must never block on anything, simply make a note of work that must be done as a result of the interrupt, clear the interrupt, then terminate, Note that it may be possible for one interrupt handler to be interrupted by a different interrupt handler, so long as they do not share data structures.) and the segment to use while running the handler (which determines the privilege level to run under). save info (153 of intel-sys.pdf) -> run handler -> resume using saved info
About What to Save: In Project 1 there are no user tasks, so the processor will be executing in PL0 all the time. This means interrupts will not cause a privilege level change, so the EFLAGS (a system register containing an assortment of important flags), current code segment, and the instruction pointer will be pushed on the stack. If Project 1 is working correctly there will not be an error code on the stack - this is for exception handlers, but a correctly-operating Project 1 will be using only interrupts. This information is indeed enough to resume executing whatever code was running when the interrupt first arrived. However, in order to service the interrupt we need to execute some code; this code will clobber the values in the general purpose registers. When we resume executing normal code, that code will expect to see the same values in the registers, as if no interrupt had ever occurred. So the first thing an interrupt handler must do is save all the general purpose registers, plus %ebp. We need not save the stack pointer because if there is no stack change (as is the case in this project since we have no user processes), the stack pointer will be correct once we pop off all of our interrupt-related information. If there is a stack change (as there will be in Project 3 when you do have user processes), the interrupt saves the stack pointer for us. So, to recap, the registers you need to save are %eax, %ebx, %ecx, %edx, %esi, %edi, and %ebp. (PUSHA, POPA, 624 and 576 of intel-isr.pdf, do that in handler using assembly, and call C handler, then restore the registers, then IRET (this instruction uses info on stack to return before interrupt)) PLEASE THINK ABOUT WHY AN ASSEMBLY WRAPPER IS USED HERE.
Assembly: comment in .S, document in .h
Note that IRQ numbers do not necessarily correspond to indices into the IDT. The PICs have the ability to map their IRQ lines to any entry in the IDT. For Project 1, the IDT entries will correspond to the timer interrupt and the keyboard interrupt.
Trap: INT n causes the processor to execute the n'th handler in the IDT.
Exception: handler also stored in IDT
Configure interrupts: An entry in the IDT can be one of three different types: a task gate (not used for p1), an interrupt gate, or a trap gate (a "gate" is just a descriptor that contains information about a transition to a different piece of code - think "gate to new procedure"). The difference between an interrupt gate and a trap gate is that when the processor accesses an interrupt handler through an interrupt gate, it clears a flag in one of the processor's registers to defer all further interrupts until the current handler returns.
For the interrupts in this project, we will be using trap gates. The format of the trap gate is given on page 151 of intel-sys.pdf. (descriptor is 64 bits long, Each gate is stored as two consecutive 32-bit words, with the first representing the least significant 32 bits of the gate) To get the base address of the IDT, we use an instruction called SIDT. We have provided a C wrapper function, idt_base(), for this instruction so you do not have to break into assembly. This is supplied in 410kern/x86/asm.h.
You will need to give careful consideration to how long it is reasonable to defer interrupts globally and how long it is reasonable to wait before acknowledging a particular interrupt.
Initially (and inconveniently for us), the PICs are programmed so that they invoke entries in the IDT that conflict with processor exceptions. The base code that runs before your kernel_main() invokes the interrupt_setup() function to remap the PICs to higher IDT entries, which agree with the constants we provide you for the timer and keyboard IDT slot numbers.
TRAP GATE TABLE DPL The privilege level required to call the handler. If it is set to 3, then user processes can call the handler directly. For gates that handle hardware interrupts (like the keyboard and timer), it should be set to 0. Offset The offset into the segment of the interrupt handler. Since all of our segments take up the whole address space, this is simply the address of your function handler. This can be obtained in C by typing the name of the function (without any parenthesis or arguments). Present (P) Must be set to 1 for a working gate. Segment Selector The segment selector for the target code segment. This defines the privilege level the handler will run at. The interrupt handler is going to be executed at PL0 and it is code, so this needs to be set to SEGSEL_KERNEL_CS. D Size of the gate. All our gates are 32 bits (D = 1).
While our support code has already put the hardware in 80x25 format. The hardware cursor is controlled by the Cathode Ray Tube Controller (CRTC), an important device on the video card. Communication with the CRTC is accomplished with a special pair of registers. Accessing these registers is a little different from writing to console memory as they use the I/O ports
The commands to send to the CRTC, as well as the location of the CRTC I/O ports, defined in 410kern/x86/video_defines.h. To hide the hardware cursor completely, simply set the position to an offset greater than the size of the console.
We need a logical cursor.
See "Console Device Driver Interface" chapter on webpage for API
must be handled quickly to not miss timer
I/O ports are defined in 410kern/x86/timer_defines.h
1193182 Hz (please memorize this number, as it will be used as an encryption key in a final exam question) For Project 1, you should configure the timer to generate interrupts every 10 milliseconds. To initialize the timer, first set its mode by sending TIMER_SQUARE_WAVE (defined in 410kern/x86/timer_defines.h) to the TIMER_MODE_IO_PORT. The timer will then expect you to send it the number of timer cycles between interrupts. This rate is a two-byte quantity, so first send it the least significant byte, then the most significant byte. These bytes should be sent to the TIMER_PERIOD_IO_PORT.
The index into the IDT for the timer is TIMER_IDT_ENTRY. You will need to fill in this IDT entry for your timer handler to execute properly.
Your timer interrupt handler should save and restore the general purpose registers. You also need to tell the PIC that you have processed the most recent interrupt that the PIC delivered. This is done by sending an INT_ACK_CURRENT to one of the PIC's I/O ports, INT_CTL_PORT. These are defined in 410kern/x86/interrupt_defines.h.
Your timer interrupt handler is required to call an application-provided clock-tick callback function each time the timer interrupt fires. This callback function will be passed an unsigned integer numTicks containing the total number of timer interrupts your handler has caught since the game kernel began running. Your driver will know the address of the callback function because it will be passed as a parameter to the handler_install() function (see below). Each application linked against your device-driver library will provide its own callback function (which could be as simple as immediately returning to your handler). Timer callbacks should run "quickly", meaning that they should return long before the next timer interrupt will arrive. (DO NOT CALL USER FUNCTION INSIDE HANDLER?)
One way (but there are plenty more!) to end up in this situation is to receive a timer interrupt before your timer driver is complete (what could you do to avoid receiving timer interrupts?).
process_scancode(NOT PURE), declared in 410kern/x86/keyhelp.h,
The index into the IDT for the keyboard is KEY_IDT_ENTRY (defined in 410kern/x86/keyhelp.h)
interrupt
read scan code from the keyboard's I/O port (KEYBOARD_PORT, also from keyhelp.h)
tell PIC I'm done: sending a INT_ACK_CURRENT to one of the PIC's I/O ports, INT_CTL_PORT (see 410kern/x86/interrupt_defines.h).
assume process_scancode() is expensive, do outside of handler
Do not forget that your keyboard interrupt handler needs to save and restore the general purpose registers!
enable and defer interrupts in 410kern/x86/asm.h (namely enable_interrupts() and disable_interrupts() respectively). You may need to use them in your keyboard driver to ensure there are no interrupt-related concurrency problems.
document the reasoning behind your use or non-use of interrupt deferral.
kernel_main() is invoked with interrupts off and thus handler_install() can assume that interrupts are off. Once handler_install() has configured your device library, kernel_main() is responsible for enabling interrupts when it is ready to receive them. In addition to kern/game.c, we have provided kern/fake.c containing empty versions of functions which you must implement and spec/p1kern.h containing the prototypes for those functions to get you started. We provide kern/fake.c only so that the code base is buildable (though not functional!) when you receive it.
lprintf() and a MAGIC_BREAK macro (in 410kern/simics/simics.h) is provided
"Console Test Program": the code found in 410kern/p1test/410_test.c, which test handler install, keyboard interaction, screen display "kern/ mini test kernel": code you provide in kern, by default kern/game.c, will run. (this file is for testing?) "Adventure": game
game.c: links with the console API, the keyboard API, and the timer device driver, which are implemented by code contained in the external modules (.o files) specified as $KERNEL_OBJS in your config.mk. Don't forget game.c must provide the callback function required by the timer API. 410test: a different kernel_main(), link against the same $OBJS list and will call your console and keyboard API functions. Before it does that, however, it will call handler_install() so you can set up your interrupt handlers and initialize global variables your drivers might need. 410kern/advent: will link against your driver objects and call handler_install().
Test:
Table of Content