Documented Function for a mode 13h switch on VGA without the BIOS.
Posted: Tue Dec 24, 2024 4:30 am
Hello everyone. After much searching, I wasn't able to find much code or an explanation on how such a thing could be done, so I decided to do it by myself and comment it as thoroughly as I can to make it easier to understand for a newcomer. I'm very much a noob at OSDev so please don't hesitate to point out any mistakes I could have made in the comments or in the function
Code: Select all
// Switches to graphics mode
// (mode 13h)
void switch_graphics()
{
// REFERENCES:
// https://ia801905.us.archive.org/30/items/bitsavers_ibmpccardseferenceManualMay92_1756350/IBM_VGA_XGA_Technical_Reference_Manual_May92.pdf
// https://wiki.osdev.org/VGA_Hardware
cli();
// MISC Output register (0x03C2):
// _______________________________
// | 7 6 5 4 3 2 1 0 |
// |VSP|HSP| 1 |---| CS |ERAM|IOS|
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// VSP = 0, HSP = 1 -> 400 vertical lines (duplicated)
// CS = 00 -> 25.175MHz clock for 320 horizontal lines
// ERAM = 1 -> Enable RAM
// IOS = 1 -> CRT controller addresses: 0x3Dx, IS1 reg: 0x3DA
outb(0x03C2, 0b01100011);
//// CRT Control registers (0x03D4):
/// Horizontal:
// Select register 0x11 (Vert. Retrace End)
// ________________________
// | 7 6 5 4 3 2 1 0|
// |PR|S5R|EVI|CVI| VRE |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// Set to 0b00001110:
// PR = 0 -> Registers 0-7 unlocked
// S5R = 0 -> Basically always zero, weird 15.75kHz display things
// EVI = 0 -> If set, generate an IRQ2 every time you finish drawing a frame
// CVI = 0 -> Clears said interrupt
// VRE = 0b1110 -> Is used when calculating when vertical retrace starts, gets set again later
outw(0x03D4, 0x0E11);
// Select register 0x00 (Horizontal Total)
// ___________________
// | 7 6 5 4 3 2 1 0 |
// | Hor. Total |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// !!! Value is the stated in the register plus 5
// HT = 95 -> 100 Characters in the horizontal scan interval (as required for mode 0x13)
outw(0x03D4, 0x5F00);
// Select register 0x01 (Hor. Display-Enable End)
// ___________________
// | 7 6 5 4 3 2 1 0 |
// | Hor.Disp.En.End |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// !!! Value is the stated in the register plus 1
// HDEE = 79 -> 80 Character positions per horizontal line (as required for mode 0x13)
outw(0x03D4, 0x4F01);
// Select register 0x02 (Hor. Blanking Start)
// ___________________
// | 7 6 5 4 3 2 1 0 |
// | Hor. Blank. St. |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// HB = 80 -> Character count where the horizontal blanking starts is 80 (as required for mode 0x13)
outw(0x03D4, 0x5002);
// Select register 0x03 (Hor. Blanking End)
// _____________________
// | 7 6 5 4 3 2 1 0 |
// | 1 |DES| EB |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// 1 = 1 -> Always 1
// DES = 00 -> No character clock skew (ignored on type 2)
// EB = 0b00010 -> End blanking field, 5 lowest bytes of value (highest is in reg 0x05), says when blanking ends
outw(0x03D4, 0x8203);
// Select register 0x04 (Start Horizontal Retrace Pulse)
// ___________________
// | 7 6 5 4 3 2 1 0 |
// | St.Hor.Ret.Pul. |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// STRP = 84 -> Used to center screen, specifies char pos when horizontal retrace goes active (as required for mode 0x13)
outw(0x03D4, 0x5404);
// Select register 0x05 (End Horizontal Retrace)
// __________________________
// | 7 6 5 4 3 2 1 0 |
// | EB5| HRD | EHR |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// EB5 = 1 -> MSB of "End blanking field" (lowest in reg 0x04)
// HRD = 0 -> No skew
// EHR = 0 -> Set's horizontal retrace to inactive at position 0 (as required for mode 0x13)
outw(0x03D4, 0x8005);
// Select register 0x13 (Offset register / Logical width)
// ___________________
// | 7 6 5 4 3 2 1 0 |
// | Offset |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// Offset = 40 -> Logical line width of screen (as required for mode 13h)
outw(0x03D4, 0x2813);
/// Vertical:
// Select register 0x06 (Vertical Total)
// ___________________
// | 7 6 5 4 3 2 1 0 |
// | Vert. Total |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// VT = 191 -> 8 low bits of number of horizontal lines on display. Upper 2 are in overflow register
outw(0x03D4, 0xBF06);
// Select register 0x07 (Overflow register)
// _______________________________________
// | 7 6 5 4 3 2 1 0 |
// | VRS9|VDE9|VT9|LC8|VBS8|VRS8|VDE8|VT8|
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// Each bit is the corresponding one from those values.
outw(0x03D4, 0x1F07);
// Select register 0x09 (Max. Scanline)
// __________________________
// | 7 6 5 4 3 2 1 0 |
// | DSC|LC9|VBS9| MSL |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// !!! Value is the stated in the register plus 1
// MSL = 1 -> 2 scanlines per character row (which is why vertical lines are duplicated in MISC)
outw(0x03D4, 0x4109);
// Vert. Retrace start (same as hor. roughly)
outw(0x03D4, 0x9C10);
// Vert. Retrace end (same as hor. roughly)
outw(0x03D4, 0x8E11);
// Vert. Display enable end (same as hor. roughly)
outw(0x03D4, 0x8F12);
// Start vert. blanking (same as hor. roughly)
outw(0x03D4, 0x9615);
// End vert. blanking (same as hor. roughly)
outw(0x03D4, 0xB916);
// Select register 0x08 (Preset row scan)
// ________________________
// | 7 6 5 4 3 2 1 0 |
// | ---| BP | SRS |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// !!! Value is the stated in the register plus 1
// BP = 00 -> Are set that way in all BIOS modes
// SRS = 0000 -> Sets starting row after a vertical retrace
outw(0x03D4, 0x0008);
/// Misc CRT regs:
// Select register 0x14 (Underline location)
// _________________________
// | 7 6 5 4 3 2 1 0 |
// | ---|DW|CB4| SUL |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// !!! Value is the stated in the register plus 1
// DW = 1 -> Use DWord for addressing
// CB4 = 0 -> Enable to divide memory counter freq. by 4
// SUL = 0 -> Horizontal scan line where underline occurs is 1
outw(0x03D4, 0x4014);
// Select register 0x17 (CRT mode control)
// __________________________________
// | 7 6 5 4 3 2 1 0 |
// | RST|WB|ADW|---|CB2|HRS|SRC|CMS0|
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// RST = 1 -> Pull to zero to disable display
// WB = 0 -> Adressing shenanigans, look at page 92 of the "IBM VGA XGA Technical Reference Manual" from 1992
// ADW = 1 -> Unlocks memory
// CB2 = 0 -> Would divide memory counter freq. by 2 if set
// HRS = 0 -> Would double resolution if set
// SRC = 1 -> Sets source of bit 14 of output multiplexer (?)
// CMS0 = 1 -> Sets source of bit 13 of output multiplexer (?)
outw(0x03D4, 0xA317);
//// Sequencer registers:
// Select register 0x04 (Memory mode)
// ________________________________
// | 7 6 5 4 3 2 1 0 |
// | ---|---|---|---|CH4|OE|EM|---|
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// CH4 = 1 -> Causes the 2 low order bits of adress to select map, making mode appear linear
// OE = 1 -> Don't use Odd/Even, access data in maps sequentially
// EM = 1 -> Extended memory (use 256K)
outw(0x03C4, 0x0E04);
// Select register 0x01 (Clocking mode)
// _____________________________
// | 7 6 5 4 3 2 1 0 |
// | ---|---|SO|SH4|DC|SL|1|D89|
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// SO = 0 -> Would turn the screen off (hence the name)
// SH4 = 0 -> Should always be cleared
// DC = 0 -> Would divide dot clock by 2
// SL = 0 -> Should always be cleared
// D89 = 1 -> Should be set unless using modes 0,1,2,3 and 7
outw(0x03C4, 0x0301);
// Select register 0x02 (Map mask)
// __________________________________
// | 7 6 5 4 3 2 1 0 |
// | ---|---|---|---|M3E|M2E|M1E|M0E|
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// MXE = 1 -> Enable map X (enable all)
outw(0x03C4, 0x0f02);
//// Graphics controller registers:
// Select register 0x05 (Graphics mode)
// _______________________________
// | 7 6 5 4 3 2 1 0 |
// | ---|C256|SR|OE|RM|---| WM |
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// C256 = 1 -> Enable 256-color mode
// SR = 0 -> Only used in modes 4 and 5
// OE = 0 -> Same as the other OE
// RM = 0 -> Do NOT use Color Compare registers
// WM = 0 -> "Each memory map is written with the system data rotated by the count
// in the Data Rotate register. If the set/reset function is enabled for a
// specific map, that map receives the 8-bit value contained in the
// Set/Reset register" (page 103 of tech manual)
outw(0x03CE, 0x4005);
// Select register 0x06 (MISC Register)
// _______________________________
// | 7 6 5 4 3 2 1 0 |
// | ---|---|---|---| MM |OE|GM|
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// MM = 01 -> A000 -> AFFF
// OE = 0 -> Do i need to keep explaining it
// GM = 1 -> We are in a graphics mode :)
outw(0x03CE, 0x0506); // graph mode & A000-AFFF
//// Attribute controller registers:
inb(0x03DA); // Clear flip flop from Adress Register (flip flop says which register you access)
outb(0x03C0, 0x30); // IPAS = 1 -> Set normal operation, select register 0x10 # Address
outb(0x03C0, 0x41); // PW = 1 -> 256-color mode, G = 1 -> Graphics mode selected # 0x10
outb(0x03C0, 0x33); // IPAS = 1, select register 0x13 # Address
outb(0x03C0, 0x00); // Do not shift the image to the left (shift it by 0) # 0x13
//// Set palette (same as text mode, 16 colors):
for (int i = 0; i < 15; i++)
{
unsigned char base = 16*i;
g_set_color(base + BLACK, 0, 0, 0);
g_set_color(base + BLUE, 0, 0, 168);
g_set_color(base + GREEN, 0, 168, 0);
g_set_color(base + CYAN, 0, 168, 168);
g_set_color(base + RED, 168, 0, 0);
g_set_color(base + PURPLE, 168, 0, 168);
g_set_color(base + BROWN, 168, 84, 0);
g_set_color(base + GRAY, 168, 168, 168);
g_set_color(base + DARK_GRAY, 84, 84, 84);
g_set_color(base + LIGHT_BLUE, 84, 84, 252);
g_set_color(base + LIGHT_GREEN, 84, 252, 84);
g_set_color(base + LIGHT_CYAN, 84, 252, 252);
g_set_color(base + LIGHT_RED, 252, 84, 84);
g_set_color(base + LIGHT_PURPLE, 252, 84, 252);
g_set_color(base + YELLOW, 252, 168, 84);
g_set_color(base + WHITE, 252, 252, 252);
}
outb(0x03C0, 0x20); // Set IPAS = 1 again, making sure output color is indeed enabled # Address
graphicsMode = GRAPHICS;
sti();
return;
}
// Sets a color in the palette for graphic modes.
// R, G and B will be compressed to 0-63 from 0-255
void g_set_color(unsigned char color_number, unsigned char R, unsigned char G, unsigned char B)
{
outb(0x03C6, 0xff); // Mask all registers to allow updating
outb(0x03C8, color_number); // Select color
outb(0x03C9, R / 4); // Write values sequentially
outb(0x03C9, G / 4);
outb(0x03C9, B / 4);
}