Bochs VESA/VBE缺少DDC兼容性
我正在尝试检查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。