VGA programming problems

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
abstractmath
Member
Member
Posts: 46
Joined: Mon Sep 07, 2020 5:50 pm

VGA programming problems

Post by abstractmath »

Hello, I'm currently trying to write a simple VGA display driver for my OS, and I appear to be having some problems whenever I write to the CRT controller registers (It causes the emulator to reboot.) I don't quite know what's going on, as it looks like I'm writing the correct data to the ports, so I'm very confused. Is there some kind of initialization process for VGA that I wasn't made aware of? Also, here's the code I'm using for my driver.

Code: Select all

#include "vga.h"
#include "../kernel/port.h"

//This code was adapted from a tutorial by Create Your Own Operating System on YouTube (https://www.youtube.com/watch?v=N68cYNWZgy8&ab_channel=WriteyourownOperatingSystem)

uint16_t miscPort;
uint16_t crtcIndexPort;
uint16_t crtcDataPort;
uint16_t sequencerIndexPort;
uint16_t sequencerDataPort;
uint16_t graphicsControllerIndexPort;
uint16_t graphicsControllerDataPort;
uint16_t attributeControllerIndexPort;
uint16_t attributeControllerReadPort;
uint16_t attributeControllerWritePort;
uint16_t attributeControllerResetPort;

bool vgad_supports_mode(uint32_t width, uint32_t height, uint32_t colordepth){
    //Just implment 320 x 200 x 8 for now
    return width == 320 && height == 200 && colordepth == 8;
}

//TODO make this more advanced
bool vgad_set_mode(uint32_t width, uint32_t height, uint32_t colordepth){
    if (!vgad_supports_mode(width, height, colordepth)){
        return false;
    }

    uint8_t g_320x200x256[] =
    {
        /* MISC */
            0x63,
        /* SEQ */
            0x03, 0x01, 0x0F, 0x00, 0x0E,
        /* CRTC */
            0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F,
            0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x9C, 0x0E, 0x8F, 0x28, 0x40, 0x96, 0xB9, 0xA3,
            0xFF,
        /* GC */
            0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
            0xFF,
        /* AC */
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
            0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
            0x41, 0x00, 0x0F, 0x00, 0x00
    };

    vgad_write_registers(g_320x200x256);
    return true;
}

void vgad_write_registers(uint8_t *registers){
    p_write8(miscPort, *(registers++));

    for (uint8_t i = 0; i < 5; i++){
        p_write8(sequencerIndexPort, i);
        p_write8(sequencerDataPort, *(registers++));
    }

//Problems here
    p_write8(crtcIndexPort, 0x03);
    p_write8(crtcDataPort, p_read8(crtcDataPort) | 0x80);
    p_write8(crtcIndexPort, 0x11);
    p_write8(crtcDataPort, p_read8(crtcDataPort) & ~0x80);

    registers[0x03] = registers[0x03] | 0x80;
    registers[0x11] = registers[0x11] & ~0x80;

    for (uint8_t i = 0; i < 25; i++){
        p_write8(crtcIndexPort, i);
        p_write8(crtcDataPort, *(registers++));
    }


    for (uint8_t i = 0; i < 9; i++){
        p_write8(graphicsControllerIndexPort, i);
        p_write8(graphicsControllerDataPort, *(registers++));
    }

    for (uint8_t i = 0; i < 21; i++){
        p_read8(attributeControllerResetPort);
        p_write8(attributeControllerIndexPort, i);
        p_write8(attributeControllerWritePort, *(registers++));
    }

    p_read8(attributeControllerResetPort);
    p_write8(attributeControllerIndexPort, 0x20);
}

uint8_t* vgad_get_framebuffer_segment(){
    p_write8(graphicsControllerIndexPort, 0x06);
    uint8_t segmentNumber = ((p_read8(graphicsControllerDataPort) >> 2) & 0x03);
    switch (segmentNumber)
    {
    default:
    case 0:
        return (uint8_t*)0x0000;
    case 1:
        return (uint8_t*)0xA000;
    case 2:
        return (uint8_t*)0xB000;
    case 3:
        return (uint8_t*)0xB800;
    } 
}

void vgad_put_pixel(uint32_t x, uint32_t y, uint8_t r, uint8_t g, uint8_t b){
    vgad_put_pixel_index(x, y, vgad_get_color_index(r, g, b));
}

void vgad_put_pixel_index(uint32_t x, uint32_t y, uint8_t colorIndex){
    uint8_t *pixelAddress = vgad_get_framebuffer_segment() + 320 * y + x;
    *pixelAddress = colorIndex; 
}

uint8_t vgad_get_color_index(uint8_t r, uint8_t g, uint8_t b){
    //This is stupid, don't do this
    if (r == 0x00 && g == 0x00 && b == 0xA8){
        return 0x01;
    }

    return 0x0;
}

void vgad_init(){
    miscPort = 0x3C2;
    crtcIndexPort = 0x3D4;
    crtcDataPort = 0x3D5;
    sequencerIndexPort = 0x3C4;
    sequencerDataPort = 0x3C5;
    graphicsControllerIndexPort = 0x3CE;
    graphicsControllerDataPort = 0x3CF;
    attributeControllerIndexPort = 0x3C0;
    attributeControllerReadPort = 0x3C1;
    attributeControllerWritePort = 0x3C0;
    attributeControllerResetPort = 0x3DA;
}
Any help regarding this would be much appreciated! Thanks!
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: VGA programming problems

Post by Octocontrabass »

abstractmath wrote:(It causes the emulator to reboot.)
Fiddling with VGA registers should not be able to reboot the emulator. Check your emulator logs (and enable more logs if necessary) to see exactly why it's rebooting.

You can post the logs here if you're not sure what they mean.
abstractmath
Member
Member
Posts: 46
Joined: Mon Sep 07, 2020 5:50 pm

Re: VGA programming problems

Post by abstractmath »

Alright, so I'm not entirely sure how to get qemu to show more logging information, but I got it to dump the registers after a cpu reset and an interrupt. These are the logs I've been getting:

Code: Select all

check_exception old: 0xd new 0xd
    16: v=08 e=0000 i=0 cpl=0 IP=0008:0000000000006565 pc=0000000000006565 SP=0010:000000000008ffb0 env->regs[R_EAX]=0000000000000000
EAX=00000000 EBX=00001000 ECX=00090000 EDX=00000000
ESI=00000000 EDI=00000000 EBP=0008ffc8 ESP=0008ffb0
EIP=00006565 EFL=00000206 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00007c6e 00000017
IDT=     00006460 00000800
CR0=00000033 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=00000000 CCD=00000053 CCO=ADDB    
EFER=0000000000000000
check_exception old: 0x8 new 0xd
Triple fault

And this is the command I'm using to get these logs:
qemu-system-x86_64 -no-reboot -d int,cpu_reset -fda os.bin

I'm not entirely sure what settings I should use for debugging, if I'm being totally honest. If any more information is necessary, please let me know and I will get it!
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: VGA programming problems

Post by Octocontrabass »

You've cut off the part of the log that shows the initial fault, so it's hard to say for certain, but it looks like the instruction at address 0x6565 caused #GP which then caused a triple fault due to an issue with your exception handlers.

Examining the instruction at address 0x6565 using objdump or a debugger might provide further insight.
abstractmath
Member
Member
Posts: 46
Joined: Mon Sep 07, 2020 5:50 pm

Re: VGA programming problems

Post by abstractmath »

Okay, so after doing a disassembly of os.bin (This is the entire binary file of my OS), there isn't an instruction at the address 0x6565? However, looking through the disassembly for some kind of jump instruction to that address yields no results either! The only thing I could find involving this number was

Code: Select all

add ax, 6565
.

I'm not sure what else to do at this point. Here is the link to my project on github, if anyone would like to take a look at it for themselves: https://github.com/AsherBearce/ToyOperatingSystem
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: VGA programming problems

Post by Octocontrabass »

abstractmath wrote:Okay, so after doing a disassembly of os.bin
How did you disassemble it? It's a flat binary, so the disassembler doesn't know where you're going to load it. If you didn't tell the disassembler the correct load address, it won't show the correct addresses either.

If you did disassemble it with the correct load address and there's still no instruction at address 0x6565, you'll have to start debugging it while it's running. There are a few different options, depending on which virtual machine you use.
abstractmath
Member
Member
Posts: 46
Joined: Mon Sep 07, 2020 5:50 pm

Re: VGA programming problems

Post by abstractmath »

I just did ndisasm os.bin > disasm. I'm not sure what you mean by the "load address", could you elaborate a little more on that?
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: VGA programming problems

Post by Octocontrabass »

Flat binaries don't have any information about where in memory they will be executed. Your bootloader loads the OS kernel to address 0x1000, but ndisasm has no way to know that if you don't tell it. Additionally, ndisasm assumes 16-bit code by default, but your kernel is 32-bit code.

Try adding "-o 0x1000" and "-b 32" to your ndisasm command line. The results should look much more like real code.
abstractmath
Member
Member
Posts: 46
Joined: Mon Sep 07, 2020 5:50 pm

Re: VGA programming problems

Post by abstractmath »

Okay, I went ahead and disassembled it correctly. Now, there seems to be an instruction which moves 0x8e into that memory address, although my code still does not have 6565 as an address.
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: VGA programming problems

Post by Octocontrabass »

abstractmath wrote:Now, there seems to be an instruction which moves 0x8e into that memory address,
That happens to be the opcode for a MOV to a segment register, which could very easily cause a GPF since it's unlikely to load a valid selector (since it's not supposed to be executed as code). And taking a quick look at your code, I notice that the value 0x8e shows up quite often in your IDT setup. And your IDT happens to have a base address of 0x6460...

So, from the look of things, your code is jumping into your IDT somehow. It's hard to say exactly what address it's jumping to, since it could execute garbage instructions for a while before it finally hits the one at address 0x6565 and crashes. This is probably a good time to start using a debugger and stepping through your code until you see it jump into nonsense.

If you're really lucky, the rest of that QEMU log might tell you more.
abstractmath
Member
Member
Posts: 46
Joined: Mon Sep 07, 2020 5:50 pm

Re: VGA programming problems

Post by abstractmath »

I'm having a very difficult time using the GDB for debugging. I'm following the instructions on the site you sent me (here: https://wiki.osdev.org/Kernel_Debugging), however when I try to use the step command, I get "Cannot find bounds of current function". I don't know what this means and googling doesn't get me anywhere either. What am I doing wrong?
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: VGA programming problems

Post by Octocontrabass »

That error means the CPU isn't executing code that GDB has debugging information for. That could indicate a problem with your debugging symbols, but it could also mean you're trying to step through assembly code. Either way, you can use "stepi" instead of "step" to move forward one instruction at a time.

A flat binary doesn't include any debugging symbols, so you might need to change your linker script.

The virtual machine has an entire BIOS to run before it gets around to loading your OS, so you should use a breakpoint to skip all of that.
abstractmath
Member
Member
Posts: 46
Joined: Mon Sep 07, 2020 5:50 pm

Re: VGA programming problems

Post by abstractmath »

I'm setting a break point to 0x1000, where the kernel gets loaded, however it's doing weird stuff, and jumping to an address that isn't even the beginning of an instruction. What I'm not understanding is why setting the CRTC register on the GPU is causing this weird code to get generated where it's getting past two, what are essentially jmp $ instructions.
abstractmath
Member
Member
Posts: 46
Joined: Mon Sep 07, 2020 5:50 pm

Re: VGA programming problems

Post by abstractmath »

So, after playing with some of my code, I noticed something really strange which I think reveal the true issue. I started out by going to square one: printing a simple message using my screen_print function. I discovered that almost none of the functions in my screen header file were working. So, I commented out everything that didn't have anything to do with printing, including the include directives. Still didn't work. Then, I actually moved the vga driver source and header files outside my project, and boom, it worked. Upon re-including these files into my project, I got the same behavior as before. So, it seems like just having these driver files being compiled into my project is somehow causing my kernel to behave badly.
abstractmath
Member
Member
Posts: 46
Joined: Mon Sep 07, 2020 5:50 pm

Re: VGA programming problems

Post by abstractmath »

Alrighty, I have solved the problem! It turns out, I wasn't loading the code correctly from my boot sector. I was only reading 15 or 16 sectors from a floppy disk, where as I should have been reading more. I appreciate the help with debugging!
Post Reply