mov eax , cr0
or eax , 0x01
mov cr0 , eax



back to months list

Project : The "Microkernel" Operating System

Journal Entry Date : 2025.04.13

It's been a very long time since I devloped operating system. This means that we need to re-organize our "grand" plan...

The Task Management Systems

  1. Merge the Page System & Node System. In order to efficiently allocate the memory for applications, we need to implement the page allocator alongside with the pre-existing node-based memory manager. As I already implemented the page allocator previously, what I need to do is just to harmonize it with the pre-existing ones!
  2. Develop Page Table Allocator. After implementing page "allocator," we now need to implement the system that creates the page table for each applications.
  3. (finally) Develop Task Management System. This is the most important and time-consuming part!
  4. Implement System Calls & Executables, etc.. This also is very time-consuming part!!

Device Drivers

Aside from developing task management system, I also want to focus on developing device drivers(because they're kinda cool.)

  1. PCI Driver Essential for the driver development!
  2. xHCI Driver Call me crazy. I'm attempting to develop a USB driver. Of course it's going to be hard, but you never know until you try it!
  3. ACPI Driver Another crazy side-project. As I don't know much about ACPI, I'm sure I'm going to be learning so Soo many things..

So... for today, I tried implementing the PCI driver that simply detects all the PCI devices in the system.

There're distinct two parts of PCI driver. One is about reading configuration space, and other is about probing the device. Probing the device will be the core kernel's feature, and fetching the configuration space will be arch-dependent part. So, I created both pci.cpp and pci.hpp to x86_64 arch folder, and also kernel folder.

This is the arch-dependent PCI driver part :

#include <kernel/driver/pci.hpp>
#include <x86_64/pci.hpp>
#include <kernel/io_port.hpp>

dword pci::read_config_space_dword(byte bus , byte device , byte function , byte offset) {
/* To-do : Support more than one configuration space access methods */
    dword config_addr = (offset & 0b11111100)
    |(((dword)function & 0b111) << 8)
    |(((dword)device & 0x1F) << 11)
    |(((dword)bus & 0xFF) << 16)
    |(1 << 31);

    io_write_dword(x86_64_PCI_CONFIG_ADDR , config_addr);
    return io_read_dword(x86_64_PCI_CONFIG_DATA);
}

For Intel architecture, there's several mechanisms to accessing the configuration space, but I chose the easiest method.

The configuration space is a 256-byte memory space that has all the information needed to drive the device. The structure of the configuration space is described in the PCI specification in detail. Basically, there's a "common header field" describing the vendor id, device id, type of the header, and miscellaneous informations. The structure of the configuration space changes by the provided type of the header. This article from osdev wiki explains the overall PCI system quite well. I wrote my kernel code based on it, and you also might want to check it out!

That function(read_config_space_dword) reads the bytes from the configuration space of specific device given by bus, function, and device number. So, the way I'm currently probing the devices in the systems is to search all the possible devices. Since bus, device, and function can range from only 0~256, there's theoretically 16,777,216 devices there, and we can just brute-force out all the devices, check them if they're available and skip those that are invalid devices. Yes, there's more efficient way to search the devices, but I'm sticking with this simple way, for now.

static void probe_device(byte bus , byte device) {
    struct pci::common_header_field header;
    
    get_common_header_field(header , bus , device , 0);
    if(header.vendor_id == 0xffff) return; // the device does not exist
    
    probe_function(bus , device , 0);
    if((header.header_type & 0x80) == 0x80) { // multi-function device
        for(byte function = 1; function < 8; function++) {
            probe_function(bus , device , function);
        }
    }
}

static void probe_bus(byte bus) {
    for(byte device = 0; device < 32; device++) {
        probe_device(bus , device);
    }
}

void pci::probe_all_pci_devices(void) {
    // brute force way of probing all the pci devices
    for(int bus = 0; bus < 256; bus++) {
        probe_bus(bus);
    }
}

Now, theoretically if we make a kernel interface that allows device drivers to search the PCI devices, we can make a driver that can use the PCI interface to drive the devices! That'll be the future goal for me.