每一个OS都有一个宿命——运行DOOM,为了让Mos也能运行DOOM,我尝试给Mos写VGA驱动。

当然最后失败了,在这里记录一下开发的过程,如果有这方面相关经验能够解决这个问题的欢迎联系我

我不明白,请帮助我

什么是VGA驱动

VGA(Video Graphics Array)是一种视频显示标准,最初由IBM在1987年引入。它是用于计算机显示器的图形标准,定义了屏幕分辨率、色彩深度和信号传输的方式。VGA已经成为计算机显示器和图形卡的基础标准之一,尽管在现代已经被更高级的标准(如DVI、HDMI和DisplayPort)所取代。(来自GPT)

为了让Mos能够在显示设备(显示器)上显示东西,我们需要按照VGA规范给Mos提供一个VGA驱动软件。

为什么选择VGA

因为Malta(Mos运行的主板)似乎只支持这一种视频规范,通过查询qemu的文档,我们能看到mips处理器虚拟化支持这几种主板

qemu文档

注意,其中两种主板是64位处理器,而mipssim并不支持VGA。

那么怎么写一个驱动呢

首先我们要弄清楚VGA是怎么一种规范,以及怎么去控制实现了VGA规范的硬件设备。最终,我找到了这个VGA文档,为了理解怎么控制VGA便花费了两天的时间,略过这段过程,简单来说,要让VGA能够显示需要做两件事

  • 设置VGA的寄存器(VGA有上百个寄存器,通过设置它们我们可以设置VGA的参数)
  • 设置VGA的显示缓冲

控制VGA的behavior,常用的设置类型有几种,我们想让VGA能够显示一块画面,从而运行DOOM,因此我们想要的是Mode 13h(320x200 linear 256-color mode),在这里可以查询到寄存器的详细信息。

要走到这一步,我花费了大概20个小时来理解寄存器有什么作用以及在互联网上搜索VGA这个过时规范的信息(真的很难找)

写一个VGA驱动

不过走到这儿了,其他不就明朗了吗

不不不,这里才是痛苦的开始

为了设置VGA的寄存器和显示缓冲,我们需要qemu的支持,那么qemu是怎么处理主板和硬件的交互呢?

通过查询qemu的文档,我们可以找到

设置寄存器

qemu通过MMIO的方式设置寄存器,根据VGA规范,VGA寄存器会被映射到0x03c0 - 0x03df这个地址区间。

VGA还有一些比较特别的地方:由于寄存器数目众多,VGA规范规定设置大部分寄存器通过先设置一个地址寄存器,再读写一个值寄存器来设置寄存器的值,细节可以查询VGA文档,此处不赘述。

那么就直接向这个地址读写就可以了吗?

不行,实际上,大部分外设都通过MMIO的方式来实现硬件寄存器-地址的映射关系,所以我们应该写的地址是MMIO基地址+寄存器地址。

MMIO映射

那么这个基地址在哪儿呢,很遗憾,如果查询qemu文档或者查询Malta文档,都没有办法找到这个基地址,Mos关于Malta的定义中有这样的代码

/*
 * QEMU MMIO address definitions.
 */
#define MALTA_PCIIO_BASE 0x18000000
#define MALTA_FPGA_BASE 0x1f000000

这就是我们需要的基地址吗?

先说结论,是的,就是这个地址,那么这个地址是哪儿来的呢?

花费了很长的时间,搜遍了互联网,第一次见到这个地址是在qemu的源码处,malta的实现部分有以下代码

/*
     * Load BAR registers as done by YAMON:
     *
     *  - set up PCI0 I/O BARs from 0x18000000 to 0x181fffff
     *  - set up PCI0 MEM0 at 0x10000000, size 0x7e00000
     *  - set up PCI0 MEM1 at 0x18200000, size 0xbc00000
     *
     */

由此,我们不仅找到了MMIO的偏移(0x18000000),还找到了我们想要的显示缓冲的地址偏移(0x18200000)

实践

然而事情没有这么简单,经过编写代码发现

  • MMIO是有效的,可以正常写入寄存器
  • 显示缓冲的地址映射无效,写不进去

显示缓冲

前情提要,查询VGA规范可以知道,根据不同设置,显示缓冲会被映射到

00 -- A0000h-BFFFFh -- 128K
01 -- A0000h-AFFFFh -- 64K
10 -- B0000h-B7FFFh -- 32K
11 -- B8000h-BFFFFh -- 32K

类似的,我们除了这个地址,还需要一个基地址偏移,也就是上文中找到的0x18200000

那么写入不了显示缓冲到底是为什么呢?我在这个地方尝试了很多办法,最终发现了qemu的终端可以显示目前的地址映射,让我们看看映射的结果是什么样的(如果想要尝试复现这个结果,我的代码在Mos仓库的vga分支上)

在qemu的debug终端输入info mtree

address-space: memory
  0000000000000000-ffffffffffffffff (prio 0, i/o): system
    0000000000000000-0000000003ffffff (prio 0, ram): alias mips_malta_low_preio.ram @mips_malta.ram 0000000000000000-0000000003ffffff
    0000000000000000-000000001fffffff (prio -10000, i/o): empty-slot
    0000000010000000-0000000017ffffff (prio 0, i/o): alias pci0-mem0 @pci0-mem 0000000010000000-0000000017ffffff
    0000000018000000-00000000181fffff (prio 0, i/o): alias pci0-io @io 0000000000000000-00000000001fffff
    0000000018200000-000000001bdfffff (prio 0, i/o): alias pci0-mem1 @pci0-mem 0000000018200000-000000001bdfffff
    000000001be00000-000000001be00fff (prio 0, i/o): gt64120-isd
    000000001e000000-000000001e3fffff (prio 0, romd): mips_malta.bios
    000000001f000000-000000001f0008ff (prio 0, i/o): alias malta-fpga @malta-fpga 0000000000000000-00000000000008ff
    000000001f000900-000000001f00093f (prio 0, i/o): serial
    000000001f000a00-000000001f0fffff (prio 0, i/o): alias malta-fpga @malta-fpga 0000000000000a00-00000000000fffff
    000000001fc00000-000000001fffffff (prio 0, rom): bios.1fc
    0000000080000000-0000000083ffffff (prio 0, ram): mips_malta.ram

address-space: I/O
  0000000000000000-000000000000ffff (prio 0, i/o): io
      0000000000000000-0000000000000003 (prio 0, i/o): acpi-evt
      0000000000000004-0000000000000005 (prio 0, i/o): acpi-cnt
      0000000000000008-000000000000000b (prio 0, i/o): acpi-tmr
    0000000000000000-0000000000000007 (prio 0, i/o): dma-chan
    0000000000000008-000000000000000f (prio 0, i/o): dma-cont
    0000000000000020-0000000000000021 (prio 0, i/o): pic
    0000000000000040-0000000000000043 (prio 0, i/o): pit
    0000000000000060-0000000000000060 (prio 0, i/o): i8042-data
    0000000000000064-0000000000000064 (prio 0, i/o): i8042-cmd
    0000000000000070-0000000000000071 (prio 0, i/o): rtc
      0000000000000070-0000000000000070 (prio 0, i/o): rtc-index
    0000000000000081-0000000000000083 (prio 0, i/o): dma-page
    0000000000000087-0000000000000087 (prio 0, i/o): dma-page
    0000000000000089-000000000000008b (prio 0, i/o): dma-page
    000000000000008f-000000000000008f (prio 0, i/o): dma-page
    00000000000000a0-00000000000000a1 (prio 0, i/o): pic
    00000000000000b2-00000000000000b3 (prio 0, i/o): apm-io
    00000000000000c0-00000000000000cf (prio 0, i/o): dma-chan
    00000000000000d0-00000000000000df (prio 0, i/o): dma-cont
    0000000000000170-0000000000000177 (prio 0, i/o): ide
    00000000000001ce-00000000000001d1 (prio 0, i/o): vbe
    00000000000001f0-00000000000001f7 (prio 0, i/o): ide
    00000000000002f8-00000000000002ff (prio 0, i/o): serial
    0000000000000376-0000000000000376 (prio 0, i/o): ide
    0000000000000378-000000000000037f (prio 0, i/o): parallel
    00000000000003b4-00000000000003b5 (prio 0, i/o): vga
    00000000000003ba-00000000000003ba (prio 0, i/o): vga
    00000000000003c0-00000000000003cf (prio 0, i/o): vga
    00000000000003d4-00000000000003d5 (prio 0, i/o): vga
    00000000000003da-00000000000003da (prio 0, i/o): vga
    00000000000003f1-00000000000003f5 (prio 0, i/o): fdc
    00000000000003f6-00000000000003f6 (prio 0, i/o): ide
    00000000000003f7-00000000000003f7 (prio 0, i/o): fdc
    00000000000003f8-00000000000003ff (prio 0, i/o): serial
    00000000000004d0-00000000000004d0 (prio 0, i/o): elcr
    00000000000004d1-00000000000004d1 (prio 0, i/o): elcr
    0000000000000cf9-0000000000000cf9 (prio 1, i/o): reset-control
    0000000000001100-000000000000113f (prio 0, i/o): pm-smbus
    000000000000afe0-000000000000afe3 (prio 0, i/o): acpi-gpe0

address-space: cpu-memory-0
  0000000000000000-ffffffffffffffff (prio 0, i/o): system
    0000000000000000-0000000003ffffff (prio 0, ram): alias mips_malta_low_preio.ram @mips_malta.ram 0000000000000000-0000000003ffffff
    0000000000000000-000000001fffffff (prio -10000, i/o): empty-slot
    0000000010000000-0000000017ffffff (prio 0, i/o): alias pci0-mem0 @pci0-mem 0000000010000000-0000000017ffffff
    0000000018000000-00000000181fffff (prio 0, i/o): alias pci0-io @io 0000000000000000-00000000001fffff
    0000000018200000-000000001bdfffff (prio 0, i/o): alias pci0-mem1 @pci0-mem 0000000018200000-000000001bdfffff
    000000001be00000-000000001be00fff (prio 0, i/o): gt64120-isd
    000000001e000000-000000001e3fffff (prio 0, romd): mips_malta.bios
    000000001f000000-000000001f0008ff (prio 0, i/o): alias malta-fpga @malta-fpga 0000000000000000-00000000000008ff
    000000001f000900-000000001f00093f (prio 0, i/o): serial
    000000001f000a00-000000001f0fffff (prio 0, i/o): alias malta-fpga @malta-fpga 0000000000000a00-00000000000fffff
    000000001fc00000-000000001fffffff (prio 0, rom): bios.1fc
    0000000080000000-0000000083ffffff (prio 0, ram): mips_malta.ram

address-space: pci0-mem
  0000000000000000-00000000ffffffff (prio 0, i/o): pci0-mem
    00000000000a0000-00000000000affff (prio 2, ram): alias vga.chain4 @vga.vram 0000000000000000-000000000000ffff
    00000000000a0000-00000000000bffff (prio 1, i/o): vga-lowmem

address-space: gt64120_pci
  0000000000000000-ffffffffffffffff (prio 0, i/o): bus master container

address-space: piix4-isa
  0000000000000000-ffffffffffffffff (prio 0, i/o): bus master container

address-space: piix4-ide
  0000000000000000-ffffffffffffffff (prio 0, i/o): bus master container

address-space: piix4-usb-uhci
  0000000000000000-ffffffffffffffff (prio 0, i/o): bus master container

address-space: PIIX4_PM
  0000000000000000-ffffffffffffffff (prio 0, i/o): bus master container

address-space: pcnet
  0000000000000000-ffffffffffffffff (prio 0, i/o): bus master container

address-space: VGA
  0000000000000000-ffffffffffffffff (prio 0, i/o): bus master container

memory-region: mips_malta.ram
  0000000080000000-0000000083ffffff (prio 0, ram): mips_malta.ram

memory-region: pci0-mem
  0000000000000000-00000000ffffffff (prio 0, i/o): pci0-mem
    00000000000a0000-00000000000affff (prio 2, ram): alias vga.chain4 @vga.vram 0000000000000000-000000000000ffff
    00000000000a0000-00000000000bffff (prio 1, i/o): vga-lowmem

memory-region: io
  0000000000000000-000000000000ffff (prio 0, i/o): io
      0000000000000000-0000000000000003 (prio 0, i/o): acpi-evt
      0000000000000004-0000000000000005 (prio 0, i/o): acpi-cnt
      0000000000000008-000000000000000b (prio 0, i/o): acpi-tmr
    0000000000000000-0000000000000007 (prio 0, i/o): dma-chan
    0000000000000008-000000000000000f (prio 0, i/o): dma-cont
    0000000000000020-0000000000000021 (prio 0, i/o): pic
    0000000000000040-0000000000000043 (prio 0, i/o): pit
    0000000000000060-0000000000000060 (prio 0, i/o): i8042-data
    0000000000000064-0000000000000064 (prio 0, i/o): i8042-cmd
    0000000000000070-0000000000000071 (prio 0, i/o): rtc
      0000000000000070-0000000000000070 (prio 0, i/o): rtc-index
    0000000000000081-0000000000000083 (prio 0, i/o): dma-page
    0000000000000087-0000000000000087 (prio 0, i/o): dma-page
    0000000000000089-000000000000008b (prio 0, i/o): dma-page
    000000000000008f-000000000000008f (prio 0, i/o): dma-page
    00000000000000a0-00000000000000a1 (prio 0, i/o): pic
    00000000000000b2-00000000000000b3 (prio 0, i/o): apm-io
    00000000000000c0-00000000000000cf (prio 0, i/o): dma-chan
    00000000000000d0-00000000000000df (prio 0, i/o): dma-cont
    0000000000000170-0000000000000177 (prio 0, i/o): ide
    00000000000001ce-00000000000001d1 (prio 0, i/o): vbe
    00000000000001f0-00000000000001f7 (prio 0, i/o): ide
    00000000000002f8-00000000000002ff (prio 0, i/o): serial
    0000000000000376-0000000000000376 (prio 0, i/o): ide
    0000000000000378-000000000000037f (prio 0, i/o): parallel
    00000000000003b4-00000000000003b5 (prio 0, i/o): vga
    00000000000003ba-00000000000003ba (prio 0, i/o): vga
    00000000000003c0-00000000000003cf (prio 0, i/o): vga
    00000000000003d4-00000000000003d5 (prio 0, i/o): vga
    00000000000003da-00000000000003da (prio 0, i/o): vga
    00000000000003f1-00000000000003f5 (prio 0, i/o): fdc
    00000000000003f6-00000000000003f6 (prio 0, i/o): ide
    00000000000003f7-00000000000003f7 (prio 0, i/o): fdc
    00000000000003f8-00000000000003ff (prio 0, i/o): serial
    00000000000004d0-00000000000004d0 (prio 0, i/o): elcr
    00000000000004d1-00000000000004d1 (prio 0, i/o): elcr
    0000000000000cf9-0000000000000cf9 (prio 1, i/o): reset-control
    0000000000001100-000000000000113f (prio 0, i/o): pm-smbus
    000000000000afe0-000000000000afe3 (prio 0, i/o): acpi-gpe0

memory-region: malta-fpga
  0000000000000000-00000000000fffff (prio 0, i/o): malta-fpga

memory-region: vga.vram
  0000000000000000-0000000000ffffff (prio 0, ram): vga.vram

memory-region: system
  0000000000000000-ffffffffffffffff (prio 0, i/o): system
    0000000000000000-0000000003ffffff (prio 0, ram): alias mips_malta_low_preio.ram @mips_malta.ram 0000000000000000-0000000003ffffff
    0000000000000000-000000001fffffff (prio -10000, i/o): empty-slot
    0000000010000000-0000000017ffffff (prio 0, i/o): alias pci0-mem0 @pci0-mem 0000000010000000-0000000017ffffff
    0000000018000000-00000000181fffff (prio 0, i/o): alias pci0-io @io 0000000000000000-00000000001fffff
    0000000018200000-000000001bdfffff (prio 0, i/o): alias pci0-mem1 @pci0-mem 0000000018200000-000000001bdfffff
    000000001be00000-000000001be00fff (prio 0, i/o): gt64120-isd
    000000001e000000-000000001e3fffff (prio 0, romd): mips_malta.bios
    000000001f000000-000000001f0008ff (prio 0, i/o): alias malta-fpga @malta-fpga 0000000000000000-00000000000008ff
    000000001f000900-000000001f00093f (prio 0, i/o): serial
    000000001f000a00-000000001f0fffff (prio 0, i/o): alias malta-fpga @malta-fpga 0000000000000a00-00000000000fffff
    000000001fc00000-000000001fffffff (prio 0, rom): bios.1fc
    0000000080000000-0000000083ffffff (prio 0, ram): mips_malta.ram

主要关注这两个地方

address-space: memory
  0000000000000000-ffffffffffffffff (prio 0, i/o): system
    0000000000000000-0000000003ffffff (prio 0, ram): alias mips_malta_low_preio.ram @mips_malta.ram 0000000000000000-0000000003ffffff
    0000000000000000-000000001fffffff (prio -10000, i/o): empty-slot
    0000000010000000-0000000017ffffff (prio 0, i/o): alias pci0-mem0 @pci0-mem 0000000010000000-0000000017ffffff
    0000000018000000-00000000181fffff (prio 0, i/o): alias pci0-io @io 0000000000000000-00000000001fffff
    0000000018200000-000000001bdfffff (prio 0, i/o): alias pci0-mem1 @pci0-mem 0000000018200000-000000001bdfffff
    000000001be00000-000000001be00fff (prio 0, i/o): gt64120-isd
    000000001e000000-000000001e3fffff (prio 0, romd): mips_malta.bios
    000000001f000000-000000001f0008ff (prio 0, i/o): alias malta-fpga @malta-fpga 0000000000000000-00000000000008ff
    000000001f000900-000000001f00093f (prio 0, i/o): serial
    000000001f000a00-000000001f0fffff (prio 0, i/o): alias malta-fpga @malta-fpga 0000000000000a00-00000000000fffff
    000000001fc00000-000000001fffffff (prio 0, rom): bios.1fc
    0000000080000000-0000000083ffffff (prio 0, ram): mips_malta.ram

和qemu源码一样,pci的mem0和mem1,MMIO端口都正确的映射到内存的位置

然而

0000000010000000-0000000017ffffff (prio 0, i/o): alias pci0-mem0 @pci0-mem 0000000010000000-0000000017ffffff
    0000000018000000-00000000181fffff (prio 0, i/o): alias pci0-io @io 0000000000000000-00000000001fffff
    0000000018200000-000000001bdfffff (prio 0, i/o): alias pci0-mem1 @pci0-mem 0000000018200000-000000001bdfffff

这里可以看到,我们需要的地址范围:pci0-mem 0xa0000-0xbf000并没有被映射到内存区域,被映射的是pci总线的0x10000000-0x1bdfffff,所以才不能写入缓冲区域

怎么解决

我没有能够解决这个问题,如果有谁能解决,请联系我

或许这是我的代码问题,或许这是qemu源码的bug,或许我们再也没办法得知真相。