从 Int 10h/AX=4F00h 检索 VESA 视频模式列表

Retrieve list of VESA video modes from Int 10h/AX=4F00h

我正在尝试开发概念验证 os。不管怎样,在这个过程中我遇到的问题之一是 vesa 视频模式。在 vesa 告诉我们从 vbe bios 信息中获取它们并找到适合我们需要的那个之后,似乎缺少硬编码的视频模式编号。但是我无法接收视频模式,因为我不知道如何在 32 位

中使用来自 C 内核的 vbeFarPtr

这是我的内核代码:

在收到带有 int 0x10 ax=0x4f00

的信息后,我将 VbeInfoBlock 作为参数从我的第二阶段引导加载程序传递给内核

int kmain(struct VbeInfoBlock *vbeinfo)
{


    init_idt();
    SetPITSpeed(100);

    init_DTCursor();

    printf(vbeinfo->signature); // I can print VESA here means I have the vbeinfoblock

    char* str = "";

    itoa(vbeinfo->video_modes,str,16); // I want a hex dump so I convert it to hex

    printf(str); // I get "VESA" for the signature followed by a string "1053" and nothing else while the list should be like this
    // If for example video mode 0x0103, 0x0118 and 0x0115 are supported
    // The list should be as 03 01 15 01 18 01 FF FF
    // So I should atleast get some FF FF
    // My output is "VESA 1053"

    while(1);
}

如果您不知道,VbeInfoBlock 定义如下

struct VbeInfoBlock
    {
        char signature[4];  // must be "VESA" to indicate valid VBE support
        uint16_t version;           // VBE version; high byte is major version, low byte is minor version
        uint32_t oem;           // segment:offset pointer to OEM
        uint32_t capabilities;      // bitfield that describes card capabilities
        uint32_t video_modes;       // segment:offset pointer to list of supported video modes
        uint16_t video_memory;      // amount of video memory in 64KB blocks
        uint16_t software_rev;      // software revision
        uint32_t vendor;            // segment:offset to card vendor string
        uint32_t product_name;      // segment:offset to card model name
        uint32_t product_rev;       // segment:offset pointer to product revision
        char reserved[222];     // reserved for future expansion
        char oem_data[256];     // OEM BIOSes store their strings in this area
    } __attribute__ ((packed));

我无法理解这个问题。还有其他方法吗? 还是我的方法正确但我的代码不正确?

我认为问题在于 VbeInfoBlock 中的 video_modes 部分被定义为 segment:offset 对。我不知道如何在 32 位 C 代码中使用它。

(你可以索取我的第二阶段bootloader或者我原来的bootloader但是这个问题我觉得没必要)

编辑:

我在 Brendan 的回答后尝试的代码

    uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + vbeinfo->video_modes_offset;

    uint16_t *videoListPointer = (uint16_t *)physical_address;
    char chr = '[=12=]';
    while(*videoListPointer != 0xffff) {

        itoa(*videoListPointer,chr,16);
        printf(chr);
        videoListPointer++;
    }

和我的 gdt

gdt_start :
gdt_null : ; the mandatory null descriptor
dd 0x0 ; 'dd ' means define double word ( i.e. 4 bytes )
dd 0x0
gdt_code : 
dw 0xffff ; Limit ( bits 0 -15)
dw 0x0 ; Base ( bits 0 -15)
db 0x0 ; Base ( bits 16 -23)
db 10011010b ; 1st flags , type flags
db 11001111b ; 2nd flags , Limit ( bits 16 -19)
db 0x0 ; Base ( bits 24 -31)
gdt_data : 
dw 0xffff ; Limit ( bits 0 -15)
dw 0x0 ; Base ( bits 0 -15)
db 0x0 ; Base ( bits 16 -23)
db 10010010b ; 1st flags , type flags
db 11001111b ; 2nd flags , Limit ( bits 16 -19)
db 0x0 ; Base ( bits 24 -31)
gdt_end : 

gdt_descriptor :
dw gdt_end - gdt_start - 1 
dd gdt_start 

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start   


编辑 2:

图片

Screenshot of myy output

编辑 3:

我使用的代码:


int kmain(struct VbeInfoBlock *vbeinfo)
{


    init_idt();
    SetPITSpeed(100);

    init_DTCursor();

    uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + vbeinfo->video_modes_offset;

    uint16_t *videoListPointer = (uint16_t *)physical_address;
    char chr[9];

    while(*videoListPointer != 0xffff) {

        //itoa(*videoListPointer, chr,16);
        printf(*videoListPointer);
        videoListPointer++;

    }


    while(1);
}

and screenshot of my output without itoa

编辑4:

gcc -v

C:\Users\Asus>gcc -v Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=D:/MinGW/mingw32/bin/../libexec/gcc/i686-w64-mingw32/8.1.0/lto-wrapper.exe Target: i686-w64-mingw32 Configured with: ../../../src/gcc-8.1.0/configure --host=i686-w64-mingw32 --build=i686-w64-mingw32 --target=i686-w64-mingw32 --prefix=/mingw32 --with-sysroot=/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32 --enable-shared --enable-static --disable-multilib --enable-languages=c,c++,fortran,lto --enable-libstdcxx-time=yes --enable-threads=win32 --enable-libgomp --enable-libatomic --enable-lto --enable-graphite --enable-checking=release --enable-fully-dynamic-string --enable-version-specific-runtime-libs --disable-sjlj-exceptions --with-dwarf2 --disable-libstdcxx-pch --disable-libstdcxx-debug --enable-bootstrap --disable-rpath --disable-win32-registry --disable-nls --disable-werror --disable-symvers --with-gnu-as --with-gnu-ld --with-arch=i686 --with-tune=generic --with-libiconv --with-system-zlib --with-gmp=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-mpfr=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-mpc=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-isl=/c/mingw810/prerequisites/i686-w64-mingw32-static --with-pkgversion='i686-win32-dwarf-rev0, Built by MinGW-W64 project' --with-bugurl=https://sourceforge.net/projects/mingw-w64 CFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/include -I/c/mingw810/prerequisites/i686-zlib-static/include -I/c/mingw810/prerequisites/i686-w64-mingw32-static/include' CXXFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/include -I/c/mingw810/prerequisites/i686-zlib-static/include -I/c/mingw810/prerequisites/i686-w64-mingw32-static/include' CPPFLAGS=' -I/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/include -I/c/mingw810/prerequisites/i686-zlib-static/include -I/c/mingw810/prerequisites/i686-w64-mingw32-static/include' LDFLAGS='-pipe -fno-ident -L/c/mingw810/i686-810-win32-dwarf-rt_v6-rev0/mingw32/opt/lib -L/c/mingw810/prerequisites/i686-zlib-static/lib -L/c/mingw810/prerequisites/i686-w64-mingw32-static/lib -Wl,--large-address-aware' Thread model: win32 gcc version 8.1.0 (i686-win32-dwarf-rev0, Built by MinGW-W64 project)

编辑5:

Screen shot of output without *

首先,稍微更改一下结构,将 video_modes 分成 2 个字段,如下所示:

struct VbeInfoBlock {
    char signature[4];  // must be "VESA" to indicate valid VBE support
    uint16_t version;           // VBE version; high byte is major version, low byte is minor version
    uint32_t oem;           // segment:offset pointer to OEM
    uint32_t capabilities;      // bitfield that describes card capabilities

    uint16_t video_modes_offset;
    uint16_t video_modes_segment;

    uint16_t video_memory;      // amount of video memory in 64KB blocks
    uint16_t software_rev;      // software revision
    uint32_t vendor;            // segment:offset to card vendor string
    uint32_t product_name;      // segment:offset to card model name
    uint32_t product_rev;       // segment:offset pointer to product revision
    char reserved[222];     // reserved for future expansion
    char oem_data[256];     // OEM BIOSes store their strings in this area
} __attribute__ ((packed));

接下来计算视频模式列表的物理地址,像这样:

uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + vbeinfo->video_modes_offset;

接下来,尽一切努力将物理地址转换为可用作指针的虚拟地址。如果您不使用分页并且段寄存器基地址为零,那么这将是微不足道的,例如 uint16_t *videoListPointer = (uint16_t *)physical_address;。如果段寄存器基数不为零,那么您需要从物理地址中减去它们(并确保您使用“32 位无符号”减法,这样如果结果为负,它会环绕到有效的正结果) .如果使用分页,那么它将取决于分页的使用方式(例如,也许您将包含视频模式列表的物理 page/s 映射到您喜欢的任何虚拟地址)。

无论如何,一旦有了可用的指针,您就可以执行以下操作:

    while(*videoListPointer != 0xFFFF) {
        printf("0x%04X\n", *videoListPointer);
        videoListPointer++;
    }

但是;如果可行,您将得到一个无意义数字的列表(旧的 "fixed mode numbers" 已被弃用,现在任何模式编号都可以表示任何内容)。您必须使用 "int 0x10, ax = 0x4F01, Get VBE mode information" 来找出实际的模式(分辨率、色深……);并且您不能在保护模式下执行此操作,因此必须切换回实模式。

鉴于您必须切换回实模式才能理解模式编号,切换回实模式然后迭代模式编号列表(使用实模式 "segment and offset" VBE 给你的,没有任何转换)。

这是对 Brendan 回答的补充。在您的第一次编辑中,您合并了 Brendan 建议的更改并执行了以下操作:

uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + \
                            vbeinfo->video_modes_offset;

uint16_t *videoListPointer = (uint16_t *)physical_address;
char chr = '[=10=]';
while(*videoListPointer != 0xffff) {

    itoa(*videoListPointer,chr,16);
    printf(chr);
    videoListPointer++;
}

首先,char chr = '[=18=]' 仅保证分配初始化为 0 的单个字节。您确实需要一个足够大的字符缓冲区,以容纳 itoa 可能返回的最长字符串。对于十六进制,即 9 个字符,其中包括 8 个十六进制数字和 NUL(\0) 终止符。对于基数 2(二进制)的最坏情况,它是 33 个字符,包括 NUL(\0) 终止符。你可以这样声明一个缓冲区:

char buf[9];

您可以将该缓冲区传递给 itoa。如果在每个之间放置 space 字符,则更容易阅读视频模式编号。修改后的代码可能如下所示:

uint32_t physical_address = (vbeinfo->video_modes_segment << 4) + \
                            vbeinfo->video_modes_offset;

uint16_t *videoListPointer = (uint16_t *)physical_address;
char buf[9];

while(*videoListPointer != 0xffff) {        
    itoa(*videoListPointer, buf, 16);
    printf(buf);
    printf(" ");
    videoListPointer++;
}

最重要:直到我最终在 GitHub 上检查了您的所有代码,我才发现这个错误。 Brendan 建议通过替换

来拆分 VBEInfoBlock 结构的 video_modes 成员的正确更改:
    uint32_t video_modes; // segment:offset pointer to list of supported video mode

有:

    uint16_t video_modes_offset;
    uint16_t video_modes_segment;

实模式 segment:offset 对存储在内存中,偏移量后跟段。问题出在 GitHub 您通过这样做反转了偏移量和分段:

    uint16_t video_modes_segment; // segment:offset pointer to list of supported video modes
    uint16_t video_modes_offset;

什么时候应该是:

    uint16_t video_modes_offset;  // segment:offset pointer to list of supported video modes
    uint16_t video_modes_segment;

由于这个错误,您为视频模式列表计算的地址是错误的,这会导致生成不正确的列表。


如果进行了这些更改,输出应类似于:

这看起来像是一个合适的列表,特别是因为列表的末尾包括 EGA/VGA 视频模式:

0 1 2 3 4 5 6 7 D E F 10 11 12 13 6A

视频模式 8 9 A B C 通常是保留的,或者不是 QEMU 支持的标准 EGA/VGA 视频模式的一部分。模式 6A 脱颖而出,因为它恰好是标准的 VESA 800x600 16 位颜色模式。基于此,我假设我正在查看适合 QEMU 的列表。