C++手写操作系统学习笔记(三)—— PCI总线和VGA

C++手写操作系统学习笔记(三)

  • 1.代码重构
  • 2.PCI(Peripheral Component Interconnect)
    • 1.PCI总线特点
    • 2.PCI控制器
      • 1.PCI基本配置空间
      • 2.PCI读写
    • 3.PCI实现
    • 4.基址寄存器
  • 2.VGA(video graphics adaptor)
    • 1.显示适配器
    • 2.VGA实现

1.代码重构

到目前为止,代码中还有一些重复出现的部分以及存在不够抽象等问题,不符合面向对象的原则,在进一步实现更复杂的功能前,有必要进行代码的整理和重构。
1. 抽象驱动程序
所有硬件加载时都需要驱动程序,有必要抽象出驱动程序类Driver,统一封装驱动程序的共有特点,让所有驱动程序类都继承Driver。同时将所有驱动程序对象放入一个数组中进行统一管理。另外,操作系统每次启动和关闭可能不会清理掉所有数据,所以在每此启动操作系统时重置所有硬件是很有必要的。
driver.h

#ifndef __DRIVER_H
#define __DRIVER_H
    class Driver
    {   
    public:
        Driver();
        ~Driver();
        virtual void Activate();
        virtual void Deactivate();
        virtual int Reset();
        //这是所有驱动程序的共有功能,均定义为virtual函数,使得在特定驱动程序中可以重写函数内容。
    };
    class DriverManager
    {
    public:
        DriverManager();
        void AddDriver(Driver*); //系统初始化时向数组加入所有驱动程序
        void ActivateAll();//遍历数组,依次启动所有驱动程序
    private:
        Driver* driver[255];//存储驱动程序指针
        int numDrivers;//记录驱动程序数,初始化为0
    };//DriverManager初始化所有驱动程序,并将所有驱动程序加入到数组中。
#endif

函数实现比较简单,省略。
通过driver.h可以重写键盘和鼠标驱动:
keyboard.h

... ...(头文件)
    class KeyboardEventHandler
    {
    public:
        KeyboardEventHandler();
        virtual void OnKeyDown(char);
        virtual void OnKeyUp(char);
    };//将键盘操作抽象出来,供应用程序执行不同的操作。定义键盘点击和抬起操作,暂时不需要实现

    class KeyBoardDriver : public InterruptHandler,public Driver
    {
        public:
            KeyBoardDriver(InterruptManager* manager,KeyboardEventHandler* handler);
            ~KeyBoardDriver();

            virtual uint32_t HandleInterrupt(uint32_t esp);
            virtual void Activate();//原本构造函数的内容作为Activate函数内容
        private:
			... ...
            KeyboardEventHandler* handler;
    };

#endif

mouse.h

... ...(头文件)
    class MouseEventHandler
    {
    public:
        MouseEventHandler();

        virtual void OnActivate();
        virtual void OnMouseDown(uint8_t button);
        virtual void OnMouseUp(uint8_t button);
        virtual void OnMouseMove(int8_t x,int8_t y);
    };//将鼠标操作抽象出来,供应用程序执行不同的操作。定义的鼠标移动,点击和抬起等函数,暂时不需要实现

    class MouseDriver : public InterruptHandler,public Driver
    {
        public:
            MouseDriver(InterruptManager* manager,MouseEventHandler* handler);
            ~MouseDriver();
            virtual uint32_t HandleInterrupt(uint32_t esp);
            virtual void Activate();//原本构造函数的内容作为Activate函数内容
        private:
            ... ...
            MouseEventHandler* handler;
    };
    
#endif

2. 重写鼠标键盘操作
将原本的鼠标键盘操作重写到kernel.cpp中,新类继承自KeyboardEventHandler,这样使得无需直接操纵屏幕,只使用现成的程序接口即可完成对外设的操作。当后续需要鼠标键盘的其他操作时只需要定义其他继承自KeyboardEventHandler/MouseEventHandler的类即可。

class PrintKeyboardEventHandler : public KeyboardEventHandler
{
public:
    void OnKeyDown(char c){
        char* foo = (char*)" ";
        foo[0]=c;
        printf(foo);
    }
};//只是简单的输出字符
class MouseToConsole : public MouseEventHandler
{
public:
    MouseToConsole(){}
    // : x(40), y(12)

    void OnActivate(){
        uint16_t* VideoMemory = (uint16_t*)0xb8000;
        VideoMemory[y*80+x] = ((VideoMemory[y*80+x]&0xf000) >> 4) | ((VideoMemory[y*80+x]&0x0f00)<<4)|
        (VideoMemory[y*80+x]&0x00ff);
    }

    void OnMouseMove(int8_t nx,int8_t ny){//传入偏移量nx,ny
        uint16_t* VideoMemory = (uint16_t*)0xb8000;
        VideoMemory[y*80+x] = ((VideoMemory[y*80+x]&0xf000) >> 4) | ((VideoMemory[y*80+x]&0x0f00)<<4)| (VideoMemory[y*80+x]&0x00ff);
        x += nx; if(x<0) x=0; else if(x>=80) x=79;
        y += ny; if(y<0) y=0; else if(y>=25) y=24;
        VideoMemory[y*80+x] = ((VideoMemory[y*80+x]&0xf000) >> 4) | ((VideoMemory[y*80+x]&0x0f00)<<4)|(VideoMemory[y*80+x]&0x00ff);
    }
private:
    int8_t x,y;
};

3. 定义printfHex
项目中多次使用到打印16进制数(如打印中断号),在kernel.cpp中封装为函数:

void printfHex(uint8_t char)
{
    char* foo = (char*)"00";
    const char* hex = "0123456789ABCDEF";
    foo[0] = hex[(char >> 4)&0x0f];
    foo[1] = hex[char & 0x0f];
    printf(foo);
}

4.整理目录结构
创建include文件夹和src文件夹分别存储库函数(.h)和源码(.cpp),同时将有关驱动与硬件等不同代码分开存放,使整个工程文件更加标准化:
C++手写操作系统学习笔记(三)—— PCI总线和VGA_第1张图片
在makefile中创建obj文件夹存放所有中间生成的.o文件:
makefile

... ...
objects = obj/loader.o \
		  obj/kernel.o \
		  obj/gdt.o  \
		  obj/hardwarecommunication/port.o \
		  obj/hardwarecommunication/interrupts.o \
		  obj/hardwarecommunication/interruptstubs.o \
		  obj/drivers/keyboard.o \
		  obj/drivers/mouse.o \
		  obj/drivers/driver.o

obj/%.o: src/%.cpp
	mkdir -p $(@D)
	g++ $(GPPRAMS) -o $@ -c $<
... ...

.PHONY: clean
clean:
	rm -rf  mykernel.bin mykernel.iso obj  #每次clean都清理整个obj文件夹

5.使用命名空间
命名空间是一个声明性区域,为其内部的标识符(类型、函数和变量等的名称)提供一个范围。 命名空间用于将代码组织到逻辑组中,还可用于避免名称冲突,尤其是在基本代码包括多个库时。 命名空间可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。总之,使用命名空间是项目工程的标准操作。
使用namespace关键字将include下每一个文件夹都设立单独的命名空间,同时都嵌套在myos的总命名空间下,在.cpp中使用using关键字声明使用哪些命名空间中的函数即可。
6.inline函数改写port
使用inline关键字,重新定义端口读写函数为内联函数,以加快端口的调用和读取。 inline函数与普通函数的区别在于,当编译器处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插人调用语句处,就像整个函数体在调用处被重写了一遍一样。这一点有点类似于宏定义。中心思想是以空间换时间

2.PCI(Peripheral Component Interconnect)

1.PCI总线特点

当越来越多的设备被调用时,不能像鼠标键盘一样一直使用硬编码的方式调用外设,需要一种更快捷,更标准的方法:这时便引入新的硬件——PCI总线。他通过使用基址寄存器实现动态申请I/O端口和地址空间(图片来自网络)
C++手写操作系统学习笔记(三)—— PCI总线和VGA_第2张图片

PCI是外部设备互联标准的缩写,是局部总线的一种标准,是intel架构计算机重要的组成部分。 (参考微机原理)作为一种局部总线,我们关注到它的两个特点:

  • 独立于CPU:不在需要CPU对外设的直接控制
  • 即插即用功能:这是由系统和外设两方面实现的:从外设角度,制造商在外设中增加一个小型存储器存放PCI规范的配置信息;从系统角度,PCI控制器(PCI Connect Controller)可以自动测试和调用配置信息中的各种参数进行统一配置,这正是我们要区实现的功能。

2.PCI控制器

1.PCI基本配置空间

PCI的基本配置空间有256B,分为头标区(64B)和设备相关区(192B)。头标区通过多个寄存器来提供与该设备有关的信息,共三类,分别对应PCI-CardBus(用于笔记本电脑),PCI-PCI和PCI-设备。其中最重要的是第0类:PCI-设备桥。三种配置空间的通用header:(https://wiki.osdev.org/Pci)
C++手写操作系统学习笔记(三)—— PCI总线和VGA_第3张图片
PCI-设备桥配置空间:
C++手写操作系统学习笔记(三)—— PCI总线和VGA_第4张图片

2.PCI读写

PCI配置信息命令与读写主要由两个32位端口实现,分别是(0xcf8)的 CONFIG_ADDRESS,和(0xcfc)的CONFIG_DATA。CONFIG_ADDRESS 指定需要访问的配置地址,而对 CONFIG_DATA 的访问将实际生成配置访问并将数据传入或传出 CONFIG_DATA 寄存器。
PCI控制器的结构为:最多可连接8条总线,每条总线最多连接32个设备,每个设备最多可以使用8种功能:

CPU
PCI
bus1
bus2
bus3
...
bus8
device1
device2
device3
device4
device5
...
device32
function1
function2
...
function8

因此,每次读写操作都需要用3bit确定bus,5bit确定device和3bit确定function,这是CONFIG_ADDRESS结构:(https://wiki.osdev.org/Pci)

Bit 31 Bits 30-24 Bits 23-16 Bits 15-11 Bits 10-8 Bits 7-0
Enable Bit Reserved Bus Number Device Number Function Number Register Offset

由于每次只能读写配置信息中32b的数据,所以需要Regiseter Offset指定读取的偏移量。

3.PCI实现

pci.h

#ifndef __MYOS__HARDWARECOMMUNICATION__PCI_H
#define __MYOS__HARDWARECOMMUNICATION__PCI_H

#include "hardwarecommunication/interrupts.h"
#include "common/types.h"
#include "hardwarecommunication/port.h"
#include "drivers/driver.h"
namespace myos
{
    namespace hardwarecommunication
    {

class PCI_DeviceDescriptor
{
public:
   PCI_DeviceDescriptor();
   ~PCI_DeviceDescriptor();

   myos::common::uint32_t portBase;
   myos::common::uint32_t interrupt;

   myos::common::uint8_t bus;
   myos::common::uint8_t device;
   myos::common::uint8_t function;

   myos::common::uint16_t device_id;
   myos::common::uint16_t vendor_id;
   myos::common::uint8_t class_id;
   myos::common::uint8_t subclass_id;
   myos::common::uint8_t interface_id;
   myos::common::uint8_t reversion;
};//参照上图定义PCI描述符,每个描述符都对应一个bus上的一个device上的一个function。

class PCI_ConnectController
{
public:
   PCI_ConnectController();
   ~PCI_ConnectController();

   myos::common::uint32_t Read(myos::common::uint8_t bus,myos::common::uint8_t deveice, myos::common::uint8_t function, myos::common::uint8_t registeroffset);
//定义某个特定function的读操作。
   void Write(myos::common::uint8_t bus, myos::common::uint8_t deveice,myos::common::uint8_t function, myos::common::uint8_t registeroffset,myos::common::uint32_t value );
//定义某个function的写操作
   bool DeviceHasFunctions(myos::common::uint8_t bus, myos::common::uint8_t device);
//判断某个device是否有function,这将加速对某一function的选择
   void SelectDrivers(myos::drivers::DriverManager* driverManager);
//选择驱动程序使得PCI可以直接和驱动程序通信而无需CPU参与
   PCI_DeviceDescriptor GetDeviceDescriptor(myos::common::uint8_t bus, myos::common::uint8_t device, myos::common::uint8_t function);
//获取PCI描述符
private:
   Port32Bit dataPort;
   Port32Bit commandPort;
};
    }
}

pci.cpp

#include "hardwarecommunication/pci.h"

using namespace myos::drivers;
using namespace myos::common;
using namespace myos::hardwarecommunication;

PCI_DeviceDescriptor::PCI_DeviceDescriptor(){}
PCI_DeviceDescriptor::~PCI_DeviceDescriptor(){}
PCI_ConnectController::PCI_ConnectController()
: dataPort(0xcfc),
commandPort(0xcf8){}//初始化PCI控制器的两个端口
PCI_ConnectController::~PCI_ConnectController(){}

uint32_t PCI_ConnectController::Read(uint8_t bus, uint8_t function, uint8_t device, uint8_t registeroffset)
{
    uint32_t id = 1<<31 | ((bus&0xff)<<16) | ((device&0x1f)<<11) | ((function&0x07)<<8) | (registeroffset&0xfc);//最后两位为0,第一位为1
	//根据CONFIG_ADDRESS结构拼接
    commandPort.Write(id);
    uint32_t result = dataPort.Read();
    return result >> (8*(registeroffset%4));
}

void PCI_ConnectController::Write(uint8_t bus, uint8_t device, uint8_t function,uint8_t registeroffset,uint32_t value)
{
    uint32_t id = 1<<31 | ((bus&0xff)<<16) | ((device&0x1f)<<11) | ((function&0x07)<<8) | (registeroffset&0xfc);
    commandPort.Write(id);
    dataPort.Write(value); 
}//和Read一样先获取CONFIG_ADDRESS,再写入value。

bool PCI_ConnectController::DeviceHasFunctions(common::uint8_t bus, common::uint8_t device){
    return Read(bus, device, 0, 0x0E) & (1<<7);//只需返回读取到的功能0的第7位是否为1即可。
}

void printf(char* str);
void printfHex(uint8_t);

void PCI_ConnectController::SelectDrivers(DriverManager* driverManager)
{
    for(uint16_t bus = 0; bus < 8; bus++){
        for(uint8_t device = 0; device < 32; device++){
            int numFunctions = DeviceHasFunctions(bus, device) ? 8 : 1;
            for(int function = 0; function < numFunctions; function++){
           //三重循环依次选择每一个bus,每一个device上每一个function
                PCI_DeviceDescriptor dev = GetDeviceDescriptor(bus, device, function);
                if(dev.vendor_id == 0x0000 || dev.vendor_id == 0xFFFF)  continue;
                //如果没有设备则vender id将为全0或全1
                printf("PCI BUS ");  printfHex(bus & 0xFF);
                printf(", DEVICE ");  printfHex(device & 0xFF);
                printf(", FUNCTION ");  printfHex(function & 0xFF);
               
                printf(" = VENDOR "); printfHex((dev.vendor_id & 0xFF00) >> 8); printfHex(dev.vendor_id & 0xFF);
                //为了打印16位使用两次printfHex
                printf(", DEVICE "); printfHex((dev.device_id & 0xFF00) >> 8); printfHex(dev.device_id & 0xFF);
                printf("\n");
            }//选择并打印出所有bus,device,function。        
}}}
PCI_DeviceDescriptor PCI_ConnectController::GetDeviceDescriptor(uint8_t bus, uint8_t device, uint8_t function)
{
    PCI_DeviceDescriptor result;
    result.bus = bus;
    result.device = device;
    result.function = function;
    
    result.vendor_id = Read(bus, device, function, 0x00);
    result.device_id = Read(bus, device, function, 0x02);

    result.class_id = Read(bus, device, function, 0x0b);
    result.subclass_id = Read(bus, device, function, 0x0a);
    result.interface_id = Read(bus, device, function, 0x09);

    result.revision = Read(bus, device, function, 0x08);
    result.interrupt = Read(bus, device, function, 0x3c);
    
    return result;
}//根据PCI配置信息通过调整偏移量记录相应内容。

最后,在kernel.cpp中启动PCI:

... ...
    PCI_ConnectController PCIController;    
    PCIController.SelectDrivers(&drvManager);

    drvManager.ActivateAll();
    Interrupts.Activate();

    while(1);
}

启动操作系统将看到所有设备与厂商的信息,可以定位到每一种特定的设备,这无论是对进一步操纵其他外设,还是对于精准定位设备bug并获取有用的信息,都是非常友好的。

4.基址寄存器

基址寄存器是实现PCI功能的核心:由上图 (Header type 0x0) 可以看到:header从0x10开始,有6个双字作为基址寄存器,作用为:

  • 为PCI设备扩展存储空间。
  • 为PCI设备提供额外的I/O端口。

基址寄存器结构:(来自微机原理)
C++手写操作系统学习笔记(三)—— PCI总线和VGA_第5张图片

  • 可预取:允许外设在被调用前就可以被访问(如可以提前调入硬盘中的内容)

下面在我们的PCI中实现获取基址寄存器。
pci.h

... ...()
        enum BaseAddressRegisterType
        {
            MemoryMapping = 0,
            InputOutput = 1
        };//枚举类型。分别对应两种功能
        
        class BaseAddressRegister
        {
        public:
            bool prefetchable;
            myos::common::uint8_t* address;
            myos::common::uint32_t size;
            BaseAddressRegisterType type;
        };//分别对应上图的四个部分

        class PCI_ConnectController
        {
        public:
           ... ...

            myos::drivers::Driver* GetDriver(PCI_DeviceDescriptor dev, myos::hardwarecommunication::InterruptManager* interrupts);
			//获取驱动
            BaseAddressRegister GetBaseAddressRegister(myos::common::uint16_t bus, myos::common::uint16_t device, myos::common::uint16_t function, myos::common::uint16_t bar);
            //获取基址寄存器入口
        private:
        ... ...
        };

pci.cpp

void PCI_ConnectController::SelectDrivers(DriverManager* driverManager,InterruptManager* interrupts, InterruptManager* interrupts)
{//增加中断参数
... ...
     if(dev.vendor_id == 0x0000 || dev.vendor_id == 0xFFFF)  continue;

     for(int barNum = 0; barNum < 6; barNum++){//一共有6个基址寄存器
         BaseAddressRegister bar = GetBaseAddressRegister(bus, device, function, barNum);
         if(bar.address && (bar.type == InputOutput))
             dev.portBase = (uint32_t)bar.address;//address为bar的高字节,
     }
     
     Driver* driver = GetDriver(dev, interrupts);
     if(driver != 0)
         driverManager->AddDriver(driver);//如果有则加入到drivermanager
 ... ...
}
... ...
BaseAddressRegister PCI_ConnectController::GetBaseAddressRegister(uint16_t bus, uint16_t device, uint16_t function, uint16_t bar)
{
    BaseAddressRegister result;
   
    uint32_t headertype = Read(bus, device, function, 0x0E) & 0x7F;
    int maxBARs = 6 - (4*headertype); //根据headertype判断使用多少个基址寄存器(最多6个)
    if(bar >= maxBARs)
        return result;
   
    uint32_t bar_value = Read(bus, device, function, 0x10 + 4*bar);//得到32位寄存器的内容
    result.type = (bar_value & 0x1) ? InputOutput : MemoryMapping;//根据最后一位判断是哪种基址寄存器
    uint32_t temp;
      
    if(result.type == MemoryMapping){//如果是内存映射寄存器
        switch((bar_value >> 1) & 0x3){//右移一位取低两位    
            case 0: // 32 Bit Mode
            case 1: // 20 Bit Mode
            case 2: // 64 Bit Mode
                break;
        }
    }
    else{ // 如果是InputOutput
        result.address = (uint8_t*)(bar_value & ~0x3);
        result.prefetchable = false;
    }
    return result;
}

Driver* PCI_ConnectController::GetDriver(PCI_DeviceDescriptor dev, InterruptManager* interrupts)
{
    Driver* driver = 0;
	......//这里只是简单的根据vendor id,class id等信息打印出特定设备信息。


    return driver;
}

2.VGA(video graphics adaptor)

1.显示适配器

显示适配器就是我们所说的显卡,是显示器和主机连接的装置,VGA(video graphics adaptor)是IBM推出的彩色显示适配器,与大多数现今的显卡兼容,是自制操作系统编写图形界面的很好选择。最高分辨率640×350,颜色最多256k种。
通常使用VGA的方法是在bios调用0x13中断,但由于我们使用grub引导程序直接跳过了bios阶段,这样使得对VGA的操作变得十分困难:即仍然同之前一样,以直接操作硬件(写入端口)的方式操作,也不需要借助PCI,以操作文本的方式写入像素,比较简陋的实现图形界面功能。
(注意:viktor(原作者)说:在没有系统保护的情况下,理论上来说,将错误的数据传入显卡可能导致硬件损坏甚至发生爆炸)(https://wiki.osdev.org/Pci)
C++手写操作系统学习笔记(三)—— PCI总线和VGA_第6张图片

2.VGA实现

在vga.h中定义所需的端口和方法(略),每种模式端口写入内容是固定的,具体规则不作研究。总之这种操作vga的方法无趣且危险。
vga.cpp

#include 

using namespace myos::common;
using namespace myos::drivers;            
VideoGraphicsArray::VideoGraphicsArray() : 
    miscPort(0x3c2),//miscellaneous output register. 
    crtcIndexPort(0x3d4),
    crtcDataPort(0x3d5),
    sequencerIndexPort(0x3c4),
    sequencerDataPort(0x3c5),
    graphicsControllerIndexPort(0x3ce),
    graphicsControllerDataPort(0x3cf),// indexed registers
    attributeControllerIndexPort(0x3c0),
    attributeControllerReadPort(0x3c1),
    attributeControllerWritePort(0x3c0),
    attributeControllerResetPort(0x3da){}    //初始化所有VGA要使用的端口

VideoGraphicsArray::~VideoGraphicsArray(){}
           
void VideoGraphicsArray::WriteRegisters(uint8_t* registers)//绕过bios启用vga
{
    //  misc:miscellaneous output register. 
    miscPort.Write(*(registers++));
    
    // sequencer
    for(uint8_t i = 0; i < 5; i++)
    {
        sequencerIndexPort.Write(i);
        sequencerDataPort.Write(*(registers++));
    }
    
    // cathode ray tube controller:阴极射线管控制
    crtcIndexPort.Write(0x03);
    crtcDataPort.Write(crtcDataPort.Read() | 0x80);//第七位置1
    crtcIndexPort.Write(0x11);
    crtcDataPort.Write(crtcDataPort.Read() & ~0x80);
    
    registers[0x03] = registers[0x03] | 0x80;
    registers[0x11] = registers[0x11] & ~0x80;
    
    for(uint8_t i = 0; i < 25; i++){
        crtcIndexPort.Write(i);
        crtcDataPort.Write(*(registers++));
    }
    
    // graphics controller
    for(uint8_t i = 0; i < 9; i++){
        graphicsControllerIndexPort.Write(i);
        graphicsControllerDataPort.Write(*(registers++));
    }
    
    // attribute controller:属性控制
    for(uint8_t i = 0; i < 21; i++) {
        attributeControllerResetPort.Read();
        attributeControllerIndexPort.Write(i);
        attributeControllerWritePort.Write(*(registers++));
    }
    
    attributeControllerResetPort.Read();
    attributeControllerIndexPort.Write(0x20);
}//像寄存器写入固定内容。各寄存器功能:https://wiki.osdev.org/VGA_Hardware

bool VideoGraphicsArray::SupportsMode(uint32_t width, uint32_t height, uint32_t colordepth)//颜色深度:用多少位表示颜色
{
    return width == 320 && height == 200 && colordepth == 8;
}//设置支持的模式:这里只给出一种模式:320×200,256种颜色

bool VideoGraphicsArray::SetMode(uint32_t width, uint32_t height, uint32_t colordepth)
{
    if(!SupportsMode(width, height, colordepth))
        return false;
    
    unsigned char 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
    };
    
    WriteRegisters(g_320x200x256);
    return true;
}//这是320×200模式固定要像各个寄存器中写入的内容


uint8_t* VideoGraphicsArray::GetFrameBufferSegment()//获取video rom memory
{
    graphicsControllerIndexPort.Write(0x06);
    uint8_t segmentNumber = graphicsControllerDataPort.Read() & (3<<2);
    switch(segmentNumber){
        default:
        case 0<<2: return (uint8_t*)0x00000;
        case 1<<2: return (uint8_t*)0xA0000;
        case 2<<2: return (uint8_t*)0xB0000;
        case 3<<2: return (uint8_t*)0xB8000;
    }
}
            
void VideoGraphicsArray::PutPixel(int32_t x, int32_t y,  uint8_t colorIndex)
{//真正写入像素点函数
    if(x < 0 || 320 <= x|| y < 0 || 200 <= y)
        return;
    uint8_t* pixelAddress = GetFrameBufferSegment() + 320*y + x;//获取像素地址:像素地址由帧缓冲区段开始,硬编码方式写入
    *pixelAddress = colorIndex;//在像素地址处写入一个像素
}

uint8_t VideoGraphicsArray::GetColorIndex(uint8_t r, uint8_t g, uint8_t b)
{
    if(r == 0x00 && g == 0x00 && b == 0x00) return 0x00; // black
    if(r == 0x00 && g == 0x00 && b == 0xA8) return 0x01; // blue
    if(r == 0x00 && g == 0xA8 && b == 0x00) return 0x02; // green
    if(r == 0xA8 && g == 0x00 && b == 0x00) return 0x04; // red
    if(r == 0xFF && g == 0xFF && b == 0xFF) return 0x3F; // white
    return 0x00;
}//这是由rgb颜色转换到ColorIndex(使用8位描述一种颜色)的方法,暂时只写入这几种颜色。
           
void VideoGraphicsArray::PutPixel(int32_t x, int32_t y,  uint8_t r, uint8_t g, uint8_t b)
{
    PutPixel(x,y, GetColorIndex(r,g,b));
}//在x,y位置写入一个像素(一个ColoreIndex)。

void VideoGraphicsArray::FillRectangle(uint32_t x, uint32_t y, uint32_t w, uint32_t h,   uint8_t r, uint8_t g, uint8_t b)
{
    for(int32_t Y = y; Y < y+h; Y++)
        for(int32_t X = x; X < x+w; X++)
            PutPixel(X, Y, r, g, b);
}//画一个长方形 :)

最后在kernel.cpp中使用两层循环把所有像素写入:(在虚拟机中速度很慢)

... ...
    VideoGraphicsArray vga;

    drvManager.ActivateAll();
    Interrupts.Activate();

    vga.SetMode(320,200,8);
    for(uint32_t y=0;y<200;y++){
        for(uint32_t x=0;x<320;x++){
            vga.PutPixel(x,y,0x00,0x00,0xa8);
        }
    }
    while(1);
}

你可能感兴趣的:(c++,学习,linux,汇编)