I\'m a teenager who has become very interested in assembly language. I\'m trying to write a small operating system in Intel x86 assembler, and I was wondering how to write direc
PART 1
For old VGA modes, there's a fixed address to write to the (legacy) display memory area. For text modes this area starts at 0x000B8000. For graphics modes it starts at 0x000A0000.
For high-resolution video modes (e.g. those set by the VESA/VBE interface) this doesn't work because the size of legacy display memory area is limited to 64 KiB and most high-resolution video modes need a lot more space (e.g. 1024 * 768 * 32-bpp = 2.25 MiB). To get around that there's 2 different methods supported by VBE.
The first method is called "bank switching", where only part of the video card's display memory is mapped into the legacy area at any time (and you can change which part is mapped). This can be quite messy - for example, to draw one pixel you might need to calculate which bank the pixel is in, then switch to that bank, then calculate which offset in the bank. To make this worse, for some video modes (e.g. 24-bpp video modes where there's 3 bytes per pixel) only the first part of a pixel's data might be in one bank and the second part of the same pixel's data is in a different bank. The main benefit of this is that it works with real mode addressing, as the legacy display memory area is below 0x00100000.
The second method is called "Linear Framebuffer" (or just "LFB"), where the video card's entire display memory area can be accessed without any messy bank switching. You have to ask the VESA/VBE interface where this area is (and it's typically in the "PCI hole" somewhere between 0xC0000000 and 0xFFF00000). This means you can't access it in real mode, and need to use protected mode or long mode or "unreal mode".
To find the address of a pixel when you're using an LFB mode, you'd do something like "pixel_address = display_memory_address + y * bytes_per_line + x * bytes_per_pixel". The "bytes_per_line" comes from the VESA/VBE interface (and may not be the same as "horizontal_resolution * bytes_per_pixel" because there can be padding between horizontal lines).
For "bank switched" VBE/VESA modes, it becomes something more like:
pixel_offset = y * bytes_per_line + x * bytes_per_pixel;
bank_number = pixel_offset / bank_size;
pixel_starting_address_within_bank = pixel_offset % bank_size;
For some old VGA modes (e.g. the 256-colour "mode 0x13") it's very similar to LFB, except there is no padding between lines and you can do "pixel_address = display_memory_address + (y * horizontal_resolution + x) * bytes_per_pixel". For text modes it's basically the same thing, except 2 bytes determine each character and its attribute - e.g. "char_address = display_memory_address + (y * horizontal_resolution + x) * 2". For other old VGA modes (monochrome/2-colour, 4-colour and 16-colour modes) the video card's memory is arranged completely differently. It's split into "planes" where each plane contains one bit of the pixel, and (for e.g.) to update one pixel in a 16-colour mode you need to write to 4 separate planes. For performance reasons the VGA hardware supports different write modes and different read modes, and it can get complicated (too complicated to describe adequately here).
PART 2
For I/O ports (on 80x86, "PC compatibles"), there's 3 general categories. The first is "de facto standard" legacy devices which use fixed I/O ports. This includes things like the PIC chips, ISA DMA controller, PS/2 controller, PIT chip, serial/parallel ports, etc. Almost anything that describes how to program each of these devices will tell you which I/O ports the device uses.
The next category is legacy/ISA devices, where the I/O ports the devices use is determined by jumpers on the card itself, and there's no sane way to determine which I/O ports they use from software. To get around this the end-user has to tell the OS which I/O ports each device uses. Thankfully this crusty stuff has all become obsolete (although that doesn't necessarily mean that nobody is using it).
The third category is "plug & play", where there's some method of asking the device which I/O ports it uses (and in most cases, changing the I/O ports the device uses). An example of this is PCI, where there's a "PCI configuration space" that tells you lots of information about each PCI device. For this categories, there is no way anyone can determine which devices will be using which I/O ports without doing it at run-time, and changing some BIOS settings can cause any/all of these devices to change I/O ports.
Also note that an Intel CPU is only a CPU. Nothing prevents those CPUs from being used in something that is radically different to a "PC compatible" computer. Intel's CPU manuals will never tell you anything about hardware that exists outside of the CPU itself (including the chipset or devices).
Part 3
Probably the best place to go for more information (that's intended for OS developers/hobbyists) is http://osdev.org/ (their wiki and their forums).
To write directly to the screen, you should probably write to the VGA Text Mode area. This is a block of memory which is a buffer for text mode.
The text-mode screen consists of 80x25 characters; each character is 16 bits wide. If the first bit is set the character will blink on-screen. The next 3 bits then detail the background color; the final 4 bits of the first byte are the foreground (or the text character)'s color. The next 8 bits are the value of the character. This is usually code-page 737 or 437, but it could vary from system to system.
Here is a Wikipedia page detailing this buffer, and here is a link to codepage 437
Almost all BIOSes will set the mode to text mode before your system is booted, but some laptop BIOSes will not boot into text mode. If you are not already in text mode, you can set it with int10h
very simply:
xor ah, ah
mov al, 0x03
int 0x10
(The above code uses BIOS interrupts, so it has to be run in Real Mode. I suggest putting this in your bootsector.)
Finally, here is a set of routines I wrote for writing strings in protected mode.
unsigned int terminalX;
unsigned int terminalY;
uint8_t terminalColor;
volatile uint16_t *terminalBuffer;
unsigned int strlen(const char* str) {
int len;
int i = 0;
while(str[i] != '\0') {
len++;
i++;
}
return len;
}
void initTerminal() {
terminalColor = 0x07;
terminalBuffer = (uint16_t *)0xB8000;
terminalX = 0;
terminalY = 0;
for(int y = 0; y < 25; y++) {
for(int x = 0; x < 80; x++) {
terminalBuffer[y * 80 + x] = (uint16_t)terminalColor << 8 | ' ';
}
}
}
void setTerminalColor(uint8_t color) {
terminalColor = color;
}
void putCharAt(int x, int y, char c) {
unsigned int index = y * 80 + x;
if(c == '\r') {
terminalX = 0;
} else if(c == '\n') {
terminalX = 0;
terminalY++;
} else if(c == '\t') {
terminalX = (terminalX + 8) & ~(7);
} else {
terminalBuffer[index] = (uint16_t)terminalColor << 8 | c;
terminalX++;
if(terminalX == 80) {
terminalX = 0;
terminalY++;
}
}
}
void writeString(const char *data) {
for(int i = 0; data[i] != '\0'; i++) {
putCharAt(terminalX, terminalY, data[i]);
}
}
You can read up about this on this page.
This is not so simple. While BIOS provides INT 10h to write text to the screen, graphics differs from one adapter to the next. For example, you can find information for VGA http://www.wagemakers.be/english/doc/vga here. Some ancient SVGA adapters http://www.intel-assembler.it/portale/5/assembly-game-programming-encyclopedia/assembly-game-programming-encyclopedia.asp here.
A little beyond my scope but you might want to look into VESA.
I found a book that seems to answer my questions. I realized that such a book could exist after reading about it on the FreeVGA page. Here's the link: Programmer's Guide to the EGA, VGA and Super VGA cards
For general I/O ports, you have to go through the BIOS, which means interrupts. Many blue moons ago, I used the references from Don Stoner to help writing some real-mode assembly, but I burnt out on it after a few months and forgot most of what I knew.