| Operating Systems Development Series | |
|
This series is intended to demonstrate and teach operating system development from the ground up. IntroductionWelcome! :) In the previus tutorial, we looked at how to switch the processor into a 32 bit mode. We also learned how we can access up to 4 GB of memory. This is great--but, how? We have not talked about the protected mode memory map, and I have failed to mention the little "0x8:Stage3" far jump from the previous tutorial. That is, where did I get 0x8 from? Also, remember that the PC boots into real mode, which has the limitation of 16 bit registers. And, hence, 16 bit segment addressing. This limits the amount of memory you can access in real mode. Because of this, we still cannot access even up to 1 GB of memory yet. Heck, we cannot even go passed the 1 MB barrier yet! What to do? We have to enable the 20th address line. This will require direct hardware programming, so we will talk about that as well. So, This is what's on the menu:
Do to the use of high level languages, like C, being able to access more then 1 MB of memory can be critical. Because of this, enabling A20 (Address line 20) will be important! Note: Remember that we cannot access over 1 MB yet! Doing so will cause a triple fault. Also, because we are going to go over direct hardware programming, this tutorial will be a little more complicated then previous ones. Don't worry - You will get more expeirence with direct hardware programming later when we develop device drivers for the Kernel. Ready?
Get ReadyFor those who have been with me this far, I am certain you know how hard OS development is. However, we have not touched anything close to hard. All of the concepts listed here is still very basic, and yet quite advanced. However: Things are only going to get much more harder.Every single controller must be programmed a special way in order to work correctly. For example, to write (or read) a hard drive, you must first determin if it is an IDE or SCSI drive. Then, you have to determin the drive number it is, and program it using either the IDE Controller or SCSI Controller, which control the IDE and SCSI connections, respecitvley. Both of these controllers are different. To add more complexity, a "Sector" might not be 512 bytes. Hence, "Reading and writing sectors" is vauge. Then comes memory management and fragmentation. This is where Paging, Virtual Address Space, and the Memory Management Unit (MMU) comes into play. Reading and writing any drive is very different then any other drive. This is even true at the bootsector level. The typical format and file system is different between media, so code that boots from a FAT12 floppy will Not work to boot a CDFS Filesystem CD ROM. By Abstracting hardware specific (And low level code), we can make most of the code, however, work for these devices. When we say "Write a file to hard drive", we normally don't want to define what a "file" is, because we shouldn't. We shouldn't have to worry about what controller to read to/from, nor the exact location on disk. This is why abstraction is *very* important! Everything here is primairly for protected mode (i.e., it is 32 bit code), although it will work in real mode as well. Because of this, remember the rule of protected mode:
Kernel DebuggingDebugging is an art form. It provides a way of trapping problems, and fixing errors through software before they become serious. Kernel Debugging relates to debugging kernel-level Ring 0 programs. This is never an easy task.
Debuggers in High Level LanguagesMost debuggers in languages, like C and C++, provide a way of displaying varable and routine names, and their values, locations, etc during runtime. The problem? We don't have any symbolic names yet in any of our programs. We are still working at the Binary level.What this means is that we need a debugger that could work and display memory directly. Bochs has a debugger just for us. Bochs DebuggerBochs comes with a debugger called bochsdbg.exe. When you launch it, you will be given the same startup screen from Bochs.exe. Load your configuation file, and start the emulation.Bochs debugger and display window will now appear, and you should see the line: In the second line, bochs tells you the number of commands sent to it (In this case, this is the first command, so a 1 is displayed). You can type your commans here. The first line is the important line. It tells you the current instruction, absolute address, and seg:offset address. It also gives you the machine language Operation Code (Opcode) equivelant.
HELP commandThe help command gives you a list of available commands.BREAK commandThe b (break) command allows you to set breakpoints at addresses in memory. For example, if we are trying to debug our OS, we need to start at the bootloader (07c00:0). However, Bochs Debugger starts where the BIOS is at. Because of this, we will need to set a breakpoint to 0x7c00:0, and continue execution until that breakpoint is reached:The above tells us that our main() function in our bootloader is at 0x7cb5. This makes sinse because, remember that the OEM Parameter Block is between this jump instruction, and the start of main(). Knowing that the bootloader loads stage 2 at 0x500, lets break to it: Now, we are at the beginning of stage 2, and could follow the debugger with our assembly file! Cool, huh? Best of all, you can see the window dynamically update to display the output of your system. Single StepThe s (Single Step) command is used to walk through one instruction at a time:
dump_cpuThis command displays the current value of all cpu registers, including RFLAGS, General Purpose, Test, Debug, Control, and Segment registers. It also includes GDTR, IDTR, LDTR, TR, and EIP.print_stackThis displays the current values of the stack. This is critical coinsidering that we use the stack very often.ConclusionThere are more commands then this, however these are the most usefull. Learning how to use the debugger is very important, espically in the early stages like we are in now.Direct Hardware Programming - TheoryThis is where things start getting very hard in operating system development."Direct hardware programming" simply referrs to communicating directly (and controlling) individual chips. As long as these chips are programmable (In some way), we can control them. In Tutorial 7, we took a very detailed look at how the system works. We also talked about how software ports work, port mapping, the IN and OUT instructions, and I gave a huge table with common port mappings on x86 architectures. Remember that, whenever the processor recieves an IN or OUT instruction, It enables the I/O Access Line on the Control Bus, which, of course, is part of the System Bus, in the Motherboards North Bridge. Because the system bus is connected to both the Memory Controller and I/O Controller, both controllers listen for specific addresses and activated lines in the control bus. If the I/O Access line is set (Electricity runs through it--which means it is active (1), The I/O controller takes the address. The I/O Controller then gives the port address to every other device, and awaits a signal from a controller chip (meaning that it belongs to some device--so give whatever data to that device). If no controller chip responds, and the port address is set back, it is ignored. This is how port mapping works. (Please see tutorial 7 for more detail.) Also, remember that a single controller chip may be assigned a range of port addresses. Port addresses are assigned by the BIOS POST, even before the BIOS is loaded and executed. Why? Alot of devices need different types of information. Some ports may represent "registers", while others may be "data" or "ready" ports. It's ugly, I know. But it gets worse. On different systems, port addresses may vary widely. Because x86 architectures are backword compatible, basic devices (Such as keyboards and mice) are useually always the same address. More complex devices, however, may not be. Direct Hardware Programming and ControllersTo better understand how everything works, lets look at controllers. After all, we will be talking to them alot--espically in protected mode.Many PCs are based off of the early Intel 8042 Microcontroller chip. This controller chip is either embedded as an IC (Intergrated Circuit) chip, or directly in the motherboard. It is useually locaed in the South Bridge. This microcontroller communcates through a cord connecting to your keyboard, to another microcontroller chip in your keyboard. When you press a key on the keyboard, It presses down on a rubber dome setting beneath the key itself. On the underside of the rubber dome is a conductive contact that, when pressed down, comes in contact with two conductive contacts on the keyboard circuit. Because of this, current can flow through. Each key is connected by a pair of electrical lines. As each signal changes (Depending on weather keys are pressed), a make code is generated (From the series of lines). This make code is sent to the microcontroller chip inside of the keyboard, and sent through the cord connecting to the computer hardware port. It is sent through as a series of on and off electrical pusles. Depending on the clock cycles, each pulse can be converted to a series of bits representing a bit pattern. We are on the motherboard. This series of bits goes through the south bridge as electrical signals, all the way to the 8042 microcontroller. This microcontroller decodes the make code into a scan code, and stores it within an internal register. That is, our buffer. The internal registers can be an EEPROM chip, or simular, so we can electrically overwrite the data whenever we want. When booting, the BIOS POST assigns each device (Through the I/O Controller) port addresses. It does this by quering the devices. In the useual case, the BIOS POST sets this internal register at port address 0x60. This means, Whenever we refrence port 0x60, we are requesting to read from this internal register. You know the rest of the story reguarding port mapping, and IN/ OUT instructions, so lets read from that register: As you can probably guess, The 8042 Microcontroller is the Keyboard Controller. By communicating with the various of registers with the chip, we can read input from the keyboard, map scan codes, even several other things: Like enabling A20. You might be wondering why you have to communicate to the keyboard controller to enable A20. We will look at this next. Gate A20 - TheoryWhen IBM designed the IBM PC AT machines, it used there newer Intel 80286 microprocessor, which was not entirely compatible with previous x86 microprocessors when in real mode. The problem? The older x86 processors did not have address lines A20 through A31. They did not have an address bus that size yet. Any programs that go beyond the first 1 MB would appear to wrap around. While it worked back then, the 80286's address space required 32 address lines. However, if all 32 lines are accessable, we get the wrapping problem again.To fix this problem, Intel put a logic gate on the 20th address line between the processor and system bus. This logic gate got names Gate A20, as it can be enabled and disabled. For older programs, it an be disabled for programs that rely on the wrap wround, and enabled for newer programs. When booting, the BIOS enables A20 when counting and testing memory, and then disables it again before giving our operating system control. There are alot of ways to enable A20. By enabling the A20 gate, we have access to all 32 lines on the address bus, and hence, can refrence 32 bit addresses, or up to 0xFFFFFFFF - 4 GB of memory. The A20 Gate is connected to the 8042 Keyboard Controller. This allows us to go through the keyboard controller to enable and disable this logic gate. Rather then having another controller, Intel allowed access to it using another controller. This gives us a way to control the gate through software. The keyboard controller happens to be the lucky controller. Hence, and alas, in order to enable A20, we have to talk to the 8042 Keyboard Microcontroller. Keyboard Controller Programming - BasicsWe are not going to cover everything reguarding programming the keyboard microcontoller--There is simply no reason to yet. Dont worry--we will when we get into the Kernel when loading device drivers, as we will create an independent keyboard driver.What we will look at are the important keyboard controller commands and registers that we need to use to enable A20.
Keyboard Controller - Status RegisterThe status register is an 8 bit value that represents the current state of the keyboard. It is useually mapped to port address 0x60. (Remember The I/O Port table from Tutorial 7!) The 8 bit value looks like this:
Looks look closer. Waiting for commandsThe Input Buffer is useually the Command Register, which is mapped to port 0x64. If we send a command to the controller, we have to wait until the command completes before sending a new command via port 0x64. We will look at all these commands later. This is important! Not waiting for your sent commands to complete can cause unpredictable results! From data curruption, to the controller doing something you dont want. It's even possible to both a) Send the controller an unkown command (Which may provoke a triple fault), or even hardware damage (Although I have not seen this happen with keyboards).To wait for the controller to finish with the command, just check if the input buffer is empty at port 0x64. Simple enough. Looking back at the table, you should notice Bit 1: Input Buffer Status. If this bit is set, the input buffer still contains our command. If it is cleared, Our command is completed, so we can send another command. So, to wait for the controller:
Keyboard Controller - Command RegisterThe Controller's command register is mapped to Port 0x64. By getting the status via Port 0x60 (Status Register), you can determin when to send keyboard commands to the Keyboard Controller via port 0x64.The commands are:
Lets see this method in action, shall we? The Keyboard Controller Command Byte Allows direct access to the keyboards contoller onboard RAM. It is quite detailed, so we will not look at it yet until we develop an actual keyboard driver.
Keyboard Controller - Command ByteThe command Byte can be modified by sending commands 0x20 and 0x60 to the Control Register (See above table).Because the command byte is just a byte, lets look at it now:
As you can imagine, this is very low level details. Not only are ports represented by numbers (That can represent virtually anything), but commands are too. Programmers of high level languages, such as C, know of the common off by one error. This error is very easy to make here. However, we are in Ring 0, so the results can be worse then applications. Being off by one can either a) Make the controller do something you don't want, b) If the command is unkown: 1) Controller ignores it, or 2) Controller malfunctions; c) Controller just malfunctions, d) Physical hardware damage, or e) Triple fault. This is important, so lets look deeper. Cautions to look forBecause of it's emulation, most of these do not apply with Bochs, but to real hardware instead.Controller executes the wrong commandIf the controller executes the wrong command, it will useually do something you don't want. Like, perhaps, read data from a port instead of write data), which may currupt your data. For example, using in al, 0x61 instead of in al, 0x60, which will read from a different register in the keyboard microcontroller, instead of the status register (Port 0x60).Unkown Controller CommandMost controllers ignore commands it does not know, and just discards it (Clears it's command register, if any.)Some controllers may malfunction, however. Please see the "Malfunction" section for more information. Controller MalfunctionsThis happens rairly, but is possible. Two notible instances are both with the Pentium processor, including the infamous FDIV and foof bugs. The FDIV bug was an internal CPU design flaw, in which the FPU inside the processor gives incorrect results.The foof problem is more series. When the processor is given the command bytes 0xf0 0x0f 0xc7 0xc8, which is an example of an Hault and Catch Fire (HCF) instruction. (An Undocumented Instruction). Most of these instructions may lock up the processor itself, forcing the user to hard reboot. Others may cause unusual side effects from the use of these instructions. One should coinsider these problems that may happen. It does happen, and controllers are no exception. (Remember that we send instruction bytes to individual ports? For example, Port 0x64--The Command Register in the Keyboard Controller). Most of these malfunctions can easily be coinsidered "Design Flaws" of the device, though. Physical Hardware damageAlthough also rare, it is possible to infict hardware damage through software. An easy example is the floppy drive. You have to control the floppy drive motor directly through the Floppy Drive Controller (FDC). Forgetting to send the command to stop the motor can cause the floppy drive to wear out and break. Be careful!Triple FaultThe microcontroller may signal the primary processor that there is a problem via the Control Bus, in which case the processor signals an exception, which will, of course, reboot the computer.Controller problems in BochsIf there is a controller problem, Bochs will provoke a Triple fault, and log the information (The problem) into the log.For example, if you try to send an unkown command (Such as 0) to the keyboard controller: Bochs will provoke a triple fault, and log the information: "KBD" represents that the log was written by the keyboard controller device. Device DriversAs you can see, there is alot going on here. You can also see how low level this is. Lets see: Each "command" sent to a controller is just a number, that could change from different controllers. With that, there is no guarantee a controller is mapped to a specific port address.Everything here is specific to the 8042 microcontroller, which is sometimes embedded onto the motherboard. Because this microcontroller is (Kind of) standard to PCs and laptops, our code will work on most systems, but there is no guarantee it will work on all of them. In fact, it won't work on all of them. So--What will happen on those "other" computers? See the Cautions to look for section. As you can see, there are a few big problems: a) This is very nonportable. And, b) Magic Numbers everywhere. The code will become very hard to maintain because of this. The numbers could represent port addresses, data, commands, registers, etc.. there is almost no way you can know without a comment of sorts. Also, notice we have two registers, and a command byte. Each of the bits represent different things. Knowing this, and all of the above, you can imagine that this could get extremily complex. This is just a keyboard controller. And yet, we have not described everything in it yet! Imagine how much more complex a video controller, or an IDE controller is. Wait... IDE Controller? Yep. You have to go through the IDE Controller to turn the hard drives on, And determin how much hard drives there are. And, no: Code for SATA Controllers are different then code for IDE Controllers, even if they both interact with hard drives. This type of detail is simply never required in applications. However, for us, we need to work at this level. Yes, Indeed: It can get extremily complex. To create an abstraction interface between the software and hardware will fix most of these problems, and make interaction to hardware far more easier. A Device Driver is a Ring 0 program, executed by the OS, that does just this. The managment of loading drivers and a friendly envirement (To insure they do not crash) is handled by the OS. In the case for Windows XP, Windows loads its Device Drivers at the start of the Kernel. Hardware Abstraction LayersHardware Abstraction Layers (HAL) provide an interface layer between the hardware controllers, and the OS. It is useually loaded by the Kernel. Hardware Abstraction Layers are a neccessity, and is useually loaded by the Kernel. We ae going to be developing a Hardware Abstraction Layer for the Kernel, so we will look at this closer later.Resetting the Computer - The right wayOkay, you should know by now that by triple faulting the CPU, you effectivly reboot the computer. There are better methods, however, and relying on a triple fault (Which may not happen) is a very bad idea.One way of resetting the computer is through the keyboard controller. Look back up at the Commands table, shown above. You should see 0xFE - System Reset. So: By sending command 0xFE to the command register (Port 0x64), you will reboot the computer. Heres an example:
We will look at reading and writing the keyboard buffer later when we develop a driver for it. Enabling A20Wait... Havn't we already seen code for this? *Looks back through tutorial* Yep, It's there. :)Lets hide the ugly A20 stuff into its own file: A20.incNot that hard, I hope :) DemoDo to the increase in complexity, I have decided to have this demo downloadable. The current Stage2.asm has not changed that much:Because the demo does not display anything new, there is not a new picture to display. Download the latest demo (*.ZIP: 8KB) Here. ConclusionWow. Just, Wow. This tutorial is bigger then I originally expected.We looked at alot of new concepts here. We also got experience with hardware programming. Remember: This is the only way of communicating with hardware in protected mode! Good bye interrupts. Good bye BIOS. Good bye everything--we are now completely on our own. Right now, you can probably start appreciating Windows a little more :) After all, they all had to start at our level. Don't worry if you do not understand everything yet--It is complicated, I know. When we get to our Kernel, we are going to have an entire tutorial dedicated to programming the keyboard microcontroller, and writing a driver for it. Cool? The next tutorial will be much easier. We are going to put Protected Mode on hold for now, and go back to the real mode code. We will add the FAT12 loading code to load our kernel. Now that A20 is enabled, we can load it at 1MB! Also, we will get some BIOS information, and anything else that comes to mind :) See you there. Until next time,
~Mike "Crypter" Questions or comments? Feel free to Contact me. | ||||||||||||||||||||||||||||||||||||||