Bochs VESA/VBE缺少DDC兼容性

后端开发 2026-07-10

我正在尝试检查VESA VBE DDC扩展是否存在,目的是从BIOS获取EDID记录。Bochs(在v3.0版本测试过,并且是今天从Git编译的一个版本)在提供扩展存在的证据时,根本不会报告该扩展存在,即使提供了我自己显示器的一个经过验证的EDID记录,以及启用它所需的所有配置选项。我已经无数次地浏览VGABIOS-lgpl汇编代码,但我仍然无法弄清楚为什么会这样——而且当我去掉验证时,我的整个程序就会挂起。以下是到此为止的完整代码库,以及我的 bochsrc

display_library: x
floppya: 1_44=./build/lavender.img, status=inserted
boot: floppy
log: ./build/bochsout.txt
clock: sync=realtime, time0=local
magic_break: enabled=1
vga: extension=vbe, ddc=file:monitor.bin
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest.bin
memory: guest=1024, host=512m
; stub.asm -- THIS FILE WORKS!

; This file provides the "bootloader" for the installation media of Lavender,
; an extremely simple program that simply loads our installation media Lilac
; bootstrap compiler and runs that, so the kernel can be compiled successfully
; and then installed to disk.
; Author: Michael Sermir
;
; Copyright (c) 2026 the LavenderOS Project
; This program is free software: you can redistribute it and/or modify it under
; the terms of the GNU General Public License as published by the Free Software
; Foundation, either version 3 of the License, or (at your option) any later
; version.
;
; This program is distributed in the hope that it will be useful, but WITHOUT
; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
; FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License along with
; this program.  If not, see <https://www.gnu.org/licenses/>.

; The BIOS will spit us out at address 0x7C00 in Real Mode, so emit 16-bit code.
org 0x7C00
bits 16

cld ; Clear the directional flag so we process strings correctly!
xor ax, ax
mov ds, ax
mov es, ax
; Set up the stack just slightly below the bootloader.
mov ss, ax
mov sp, 0x7C00

; Give us five chances to reset the disk system. If we fail that many times, we
; will just halt the system.
; We already have the disk we are going to load/read from (passed in from the
; BIOS in the DL register), so we don't have to explicitly set that.
mov ax, 0x0005
reset_disk_system:
    int 0x13
    jnc load_bootstrapper
    dec al
    jnz reset_disk_system
    mov si, reset_failure_string
    jmp handle_fatal_error
; Give another five chances to actually load from disk.
mov di, 0x05
load_bootstrapper:
    xor bx, bx ; Load the disk at 0x0000 (the current segment).
    mov es, bx
    mov bx, 0x7E00 ; Load the disk at offset 0x7E00 (right after this program).

    mov ax, 0x0208 ; Read 8 sectors off the disk.
    mov cx, 0x0002 ; Begin at cylinder 0, sector 2.
    xor dh, dh     ; Read head 0.
    int 0x13
    jc .fail
    jmp 0x0000:0x7E00 ; Jump to the Lilac bootstrapped compiler.
    .fail:
        dec di
        jnz load_bootstrapper
        mov si, load_failure_string
        ; We don't need to jump to an address, we can just keep going.
handle_fatal_error:
    mov dl, ah  ; Save the return code so we can print it in a second.
    mov cl, 0xA ; Move 10 into CL so we can divide by it to print the error.

    mov ah, 0x0E                ; Teletype character.
    xor bx, bx                  ; Page 0 and no background color.
    .write_loop:
        lodsb     ; Load character into AL.
        or al, al ; Check AL for NUL terminator.
        jz .write_error
        int 0x10
        jmp .write_loop
    ; A very helpful function recieved from the response to a StackOverflow
    ; question. Please see: https://codereview.stackexchange.com/a/301726/297450
    .write_error:
        xor ax, ax
        mov al, dl
        shr ax, 4
        mov bx, hex_digits
        xlat
        mov ah, 0x0E
        xor bx, bx
        int 0x10

        mov al, dl
        and al, 0x0F
        mov bx, hex_digits
        xlat
        mov ah, 0x0E
        xor bx, bx
        int 0x10
    cli
    hlt

reset_failure_string: db "DISK RESET FAIL 0x", 0
load_failure_string:  db "DISK READ FAIL 0x", 0
hex_digits:           db "0123456789ABCDEF"

; Ensure the boot signature bookends the bootloader so the bootloader recognizes
; the sector as bootable.
times 510-($-$$) db 0
dw 0xAA55
; boot.asm -- THIS FILE DOESN'T (?)

org 0x7E00
bits 16

check_vbe_installation:
    mov ax, 0x4F15
    xor bl, bl
    int 0x10
    cmp al, 0x4F
    je get_vbe_edid
    mov si, vbe_missing_string
    jmp handle_fatal_error
get_vbe_edid:
    mov di, vbe_edid

    mov ax, 0x4F15
    mov bl, 0x01
    xor cx, cx
    xor dx, dx
    int 0x10
    cmp al, 0x4F
    je parse_vbe_edid
    mov si, vbe_edid_failed_string
    jmp handle_fatal_error
parse_vbe_edid:
    cli
    hlt
handle_fatal_error:
    mov dl, ah  ; Save the return code so we can print it in a second.
    mov cl, 0xA ; Move 10 into CL so we can divide by it to print the error.

    mov ah, 0x0E                ; Teletype character.
    xor bx, bx                  ; Page 0 and no background color.
    .write_loop:
        lodsb     ; Load character into AL.
        or al, al ; Check AL for NUL terminator.
        jz .write_error
        int 0x10
        jmp .write_loop
    ; A very helpful function recieved from the response to a StackOverflow
    ; question. Please see: https://codereview.stackexchange.com/a/301726/297450
    .write_error:
        xor ax, ax
        mov al, dl
        shr ax, 4
        mov bx, hex_digits
        xlat
        mov ah, 0x0E
        xor bx, bx
        int 0x10

        mov al, dl
        and al, 0x0F
        mov bx, hex_digits
        xlat
        mov ah, 0x0E
        xor bx, bx
        int 0x10
    cli
    hlt

vbe_missing_string: db "VBE MISSING 0x", 0
vbe_edid_failed_string: db "VBE EDID FAIL 0x", 0
hex_digits:         db "0123456789ABCDEF"

struc vbe_edid_detailed_timing
    horizontal_frequency:       resb 0
    vertical_frequency:         resb 0
    horizontal_active_pixels:   resb 0
    horizontal_blanking_pixels: resb 0
    horizontal_pixels_2:        resb 0 ; I do not understand the purpose of this.
    vertical_active_lines:      resb 0
    vertical_blanking_lines:    resb 0
    vertical_pixels_2:          resb 0 ; Nor this.
    horizontal_sync_offset:     resb 0
    horizontal_sync_pulsewidth: resb 0
    vertical_sync_1:            resb 0
    vertical_sync_2:            resb 0
    horizontal_image_size:      resb 0
    vertical_image_size:        resb 0
    image_size_2:               resb 0
    horizontal_border_pixels:   resb 0
    vertical_border_lines:      resb 0
    display_type:               resb 0
endstruc

vbe_edid:
    .padding:             dq 0
    .manufacturer_id:     dw 0
    .monitor_id:          dw 0
    .monitor_serial:      dd 0
    .manufacturer_week:   db 0
    .manufacturer_year:   db 0
    .structure_version:   db 0
    .structure_revision:  db 0
    .video_input_type:    db 0
    .max_horizontal_size: db 0
    .max_vertical_size:   db 0
    .gamma:               db 0
    .video_input_flags:   db 0
    .chroma:
        .green_red:  db 0
        .white_blue: db 0
        .red_y:      db 0
        .red_x:      db 0
        .green_y:    db 0
        .green_x:    db 0
        .blue_y:     db 0
        .blue_x:     db 0
        .white_y:    db 0
        .white_x:    db 0
    .timing_table_1:      db 0
    .timing_table_2:      db 0
    .manufacturer_timing: db 0
    .standard_timing:     dq 0
    .detailed_timing_1:   istruc vbe_edid_detailed_timing iend
    .detailed_timing_2:   istruc vbe_edid_detailed_timing iend
    .detailed_timing_3:   istruc vbe_edid_detailed_timing iend
    .detailed_timing_4:   istruc vbe_edid_detailed_timing iend
    .unused:              db 0
    .checksum:            db 0

times 4096-($-$$) db 0
                     Bochs x86 Emulator 3.0.devel
             Built from GitHub snapshot after release 3.0
                  Compiled on Apr  1 2026 at 23:37:27

以下标志:

--enable-sb16
--enable-ne2000
--enable-all-optimizations
--enable-cpu-level=6
--enable-3dnow
--enable-x86-64
--enable-vmx=2
--enable-svm
--enable-avx
--enable-evex
--enable-cet
--enable-pci
--enable-clgd54xx
--enable-geforce
--enable-voodoo
--enable-usb
--enable-usb-ohci
--enable-usb-ehci
--enable-usb-xhci
--enable-busmouse
--enable-es1370
--enable-e1000
--enable-plugins
--enable-show-ips
--enable-debugger
--enable-debugger-gui
--with-all-libs

我已经尝试在ES:DI上做些调整,尽管安装函数并不需要它。我也尝试调整EDID文件的路径,但没有成功。我已经想不到办法了。

解决方案

请参见 https://github.com/bochs-emu/VGABIOS/blob/master/vgabios/vbe.c#L2733 。你需要把CX=0设置为不仅用于BL=1读取数据,也用于BL=0检查扩展。Brown的中断清单在该调用中没有提及CX寄存器,但我敢断言实际的规范VESA VBE/DDC标准确实有提及(该标准在VBE Core标准中被提及,但我找不到其文本)。VGABIOS说 CX = Controller unit number,这听起来是合乎逻辑的。

编辑:确实在标准中有,而且还有更多!(VBEDDC11.PDF):

3.2.1 00 - Report VBE/DDC Capabilities
Input:
AH= 4fh VESA Extension
AL= 15h VBE/DDC Services
BL = 00h Report DDC Capabilities
CX = 00h Controller unit number (00 = primary controller) (*)
ES:DI Null pointer, must be 0:0 in version 1.0 Reserved for future use

(*) This refers to the monitor port number. When version 1.0 of the document was developed it
was assumed that one graphics controller had only one monitor port. Later controller designs
have multiple monitor ports per graphics controller. This parameter selects the monitor port
number from which the function reads an EDID block

因此这应该能帮助你通过验证。

当我去掉验证时,我的整个程序就会挂起

你这是什么意思?是在BIOS读取EDID时挂起吗?因为如果不是,那么在那之后你程序里唯一发生的就是挂起循环。

如果确实是在BIOS读取EDID时挂起,那么看起来像是Bochs的一个bug。VGABIOS会像真实硬件BIOS那样,实际从DDC读取EDID,而Bochs会模拟DDC来提供你给它的EDID。因此如果在这次交换中它挂起,就像是Bochs在该仿真中出错。如果是这种情况,最好的做法就是直接向Bochs团队提交一个bug。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章